diff --git a/.gitignore b/.gitignore index d850b2fd0..aec099e8a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # Build artifacts -agent/configure.ac agent/newrelic.map +agent/configure.ac agent/*.dep axiom/tests/*.tmp bin/ @@ -13,3 +13,4 @@ php_agent.log # Dev artifacts .vscode +*.log diff --git a/agent/fw_drupal.c b/agent/fw_drupal.c index dbc67a7ec..c2628b636 100644 --- a/agent/fw_drupal.c +++ b/agent/fw_drupal.c @@ -268,7 +268,11 @@ NR_PHP_WRAPPER(nr_drupal_http_request_before) { * this new segment is now at the top of the segment stack. */ if (NULL != NRPRG(drupal_http_request_segment)) { +#if ZEND_MODULE_API_NO < ZEND_8_2_X_API_NO NRPRG(drupal_http_request_segment)->wraprec = auto_segment->wraprec; +#else + NRPRG(drupal_http_request_segment)->execute_data = auto_segment->execute_data; +#endif } } } diff --git a/agent/fw_drupal_common.c b/agent/fw_drupal_common.c index 894ff2270..c09aac3d6 100644 --- a/agent/fw_drupal_common.c +++ b/agent/fw_drupal_common.c @@ -60,6 +60,9 @@ NR_PHP_WRAPPER(nr_drupal_wrap_module_hook) { * function such as a_b_c is ambiguous (is the module a or a_b?). Instead, * we'll see if they're defined in the wraprec. */ +#if ZEND_MODULE_API_NO >= ZEND_8_2_X_API_NO + wraprec = nr_php_get_wraprec(execute_data->func); +#endif if ((NULL != wraprec->drupal_hook) && (NULL != wraprec->drupal_module)) { nr_drupal_create_metric(auto_segment, NR_PSTR(NR_DRUPAL_MODULE_PREFIX), wraprec->drupal_module, wraprec->drupal_module_len); diff --git a/agent/fw_wordpress.c b/agent/fw_wordpress.c index 8503c5286..1f3855e94 100644 --- a/agent/fw_wordpress.c +++ b/agent/fw_wordpress.c @@ -329,6 +329,60 @@ static char* nr_wordpress_plugin_from_function(zend_function* func TSRMLS_DC) { return plugin; } +#if ZEND_MODULE_API_NO >= ZEND_8_2_X_API_NO +NR_PHP_WRAPPER(nr_wordpress_wrap_hook_core) { + NR_UNUSED_SPECIALFN; + (void)wraprec; + + /* + * We only want to hook the function being called if this is a WordPress + * function, we're instrumenting hooks, and WordPress is currently executing + * hooks (denoted by the wordpress_tag being set). + */ + NR_PHP_WRAPPER_REQUIRE_FRAMEWORK(NR_FW_WORDPRESS); + + char* tag = nr_stack_get_top(&NRPRG(wordpress_tags)); + if ((0 == NRINI(wordpress_hooks)) || (NULL == tag)) { + NR_PHP_WRAPPER_LEAVE; + } + + if (NRPRG(wordpress_core)) { + nr_wordpress_create_metric(auto_segment, NR_WORDPRESS_HOOK_PREFIX, tag); + } +} +NR_PHP_WRAPPER_END + +NR_PHP_WRAPPER(nr_wordpress_wrap_hook_plugin) { + char* plugin = NULL; + + NR_UNUSED_SPECIALFN; + + /* + * We only want to hook the function being called if this is a WordPress + * function, we're instrumenting hooks, and WordPress is currently executing + * hooks (denoted by the wordpress_tag being set). + */ + NR_PHP_WRAPPER_REQUIRE_FRAMEWORK(NR_FW_WORDPRESS); + + char* tag = nr_stack_get_top(&NRPRG(wordpress_tags)); + if ((0 == NRINI(wordpress_hooks)) || (NULL == tag)) { + NR_PHP_WRAPPER_LEAVE; + } + // Use optimized wraprec hashmap over plugin hashmap + wraprec = nr_php_get_wraprec(execute_data->func); + plugin = wraprec->wordpress_plugin_theme; + //plugin = nr_wordpress_plugin_from_function(execute_data->func); + + if (NULL != plugin) { + nr_wordpress_create_metric(auto_segment, NR_WORDPRESS_HOOK_PREFIX, tag); + nr_wordpress_create_metric(auto_segment, NR_WORDPRESS_PLUGIN_PREFIX, + plugin); + } +} +NR_PHP_WRAPPER_END +#endif + +#if ZEND_MODULE_API_NO < ZEND_8_2_X_API_NO NR_PHP_WRAPPER(nr_wordpress_wrap_hook) { #if ZEND_MODULE_API_NO < ZEND_7_4_X_API_NO zend_function* func = NULL; @@ -375,6 +429,7 @@ NR_PHP_WRAPPER(nr_wordpress_wrap_hook) { } } NR_PHP_WRAPPER_END +#endif // = ZEND_8_0_X_API_NO \ +#if ZEND_MODULE_API_NO >= ZEND_8_2_X_API_NO + if (NULL != wordpress_plugin_theme) { + callback_wraprec = nr_php_wrap_callable_before_after( + zf, NULL, nr_wordpress_wrap_hook_plugin); + } else { + callback_wraprec = nr_php_wrap_callable_before_after( + zf, NULL, nr_wordpress_wrap_hook_core); + } +#elif ZEND_MODULE_API_NO >= ZEND_8_0_X_API_NO \ && !defined OVERWRITE_ZEND_EXECUTE_DATA callback_wraprec = nr_php_wrap_callable_before_after( zf, NULL, nr_wordpress_wrap_hook); diff --git a/agent/lib_guzzle4.c b/agent/lib_guzzle4.c index c52ddffff..457054eb4 100644 --- a/agent/lib_guzzle4.c +++ b/agent/lib_guzzle4.c @@ -438,22 +438,33 @@ const zend_function_entry nr_guzzle4_subscriber_functions[] * Purpose : Registers an event subscriber for a newly instantiated * GuzzleHttp\Client object. */ + +#if ZEND_MODULE_API_NO >= ZEND_8_2_X_API_NO +void nr_guzzle4_client_construct(NR_EXECUTE_PROTO) { +#else NR_PHP_WRAPPER_START(nr_guzzle4_client_construct) { +#endif zval* emitter = NULL; zval* retval = NULL; zval* subscriber = NULL; zval* this_var = nr_php_scope_get(NR_EXECUTE_ORIG_ARGS TSRMLS_CC); +#if ZEND_MODULE_API_NO < ZEND_8_2_X_API_NO (void)wraprec; +#endif NR_UNUSED_SPECIALFN; /* This is how we distinguish Guzzle 4/5 from other versions. */ if (0 == nr_guzzle_does_zval_implement_has_emitter(this_var TSRMLS_CC)) { +#if ZEND_MODULE_API_NO < ZEND_8_2_X_API_NO NR_PHP_WRAPPER_CALL; +#endif goto end; } +#if ZEND_MODULE_API_NO < ZEND_8_2_X_API_NO NR_PHP_WRAPPER_CALL; +#endif /* * We can't have newrelic\Guzzle4\Subscriber implement @@ -505,7 +516,9 @@ NR_PHP_WRAPPER_START(nr_guzzle4_client_construct) { nr_php_zval_free(&emitter); nr_php_zval_free(&subscriber); } +#if ZEND_MODULE_API_NO < ZEND_8_2_X_API_NO NR_PHP_WRAPPER_END +#endif void nr_guzzle4_enable(TSRMLS_D) { if (0 == NRINI(guzzle_enabled)) { diff --git a/agent/lib_guzzle4.h b/agent/lib_guzzle4.h index b1f58de99..c9eef3d4c 100644 --- a/agent/lib_guzzle4.h +++ b/agent/lib_guzzle4.h @@ -26,6 +26,10 @@ extern void nr_guzzle4_rshutdown(TSRMLS_D); /* * Purpose : Client::__construct() wrapper for Guzzle 4. */ +#if ZEND_MODULE_API_NO < ZEND_8_2_X_API_NO extern NR_PHP_WRAPPER_PROTOTYPE(nr_guzzle4_client_construct); +#else +extern void nr_guzzle4_client_construct(NR_EXECUTE_PROTO); +#endif #endif /* LIB_GUZZLE4_HDR */ diff --git a/agent/lib_guzzle6.c b/agent/lib_guzzle6.c index e65a684b7..54fe17950 100644 --- a/agent/lib_guzzle6.c +++ b/agent/lib_guzzle6.c @@ -343,7 +343,11 @@ const zend_function_entry nr_guzzle6_requesthandler_functions[] /* }}} */ +#if ZEND_MODULE_API_NO >= ZEND_8_2_X_API_NO +void nr_guzzle6_client_construct(NR_EXECUTE_PROTO) { +#else NR_PHP_WRAPPER_START(nr_guzzle6_client_construct) { +#endif zval* config; zend_class_entry* guzzle_client_ce; zval* handler_stack; @@ -380,16 +384,22 @@ NR_PHP_WRAPPER_START(nr_guzzle6_client_construct) { version); nr_free(version); +#if ZEND_MODULE_API_NO < ZEND_8_2_X_API_NO (void)wraprec; +#endif NR_UNUSED_SPECIALFN; /* This is how we distinguish Guzzle 4/5. */ if (nr_guzzle_does_zval_implement_has_emitter(this_var TSRMLS_CC)) { +#if ZEND_MODULE_API_NO < ZEND_8_2_X_API_NO NR_PHP_WRAPPER_CALL; +#endif goto end; } +#if ZEND_MODULE_API_NO < ZEND_8_2_X_API_NO NR_PHP_WRAPPER_CALL; +#endif /* * Get our middleware callable (which is just a string), and make sure it's @@ -436,7 +446,9 @@ NR_PHP_WRAPPER_START(nr_guzzle6_client_construct) { nr_php_zval_free(&middleware); nr_php_scope_release(&this_var); } +#if ZEND_MODULE_API_NO < ZEND_8_2_X_API_NO NR_PHP_WRAPPER_END +#endif void nr_guzzle6_enable(TSRMLS_D) { int retval; diff --git a/agent/lib_guzzle6.h b/agent/lib_guzzle6.h index 67bff7ecf..e66c97915 100644 --- a/agent/lib_guzzle6.h +++ b/agent/lib_guzzle6.h @@ -20,6 +20,10 @@ extern void nr_guzzle6_minit(TSRMLS_D); /* * Purpose : Client::__construct() wrapper for Guzzle 6. */ +#if ZEND_MODULE_API_NO < ZEND_8_2_X_API_NO extern NR_PHP_WRAPPER_PROTOTYPE(nr_guzzle6_client_construct); +#else +extern void nr_guzzle6_client_construct(NR_EXECUTE_PROTO); +#endif #endif /* LIB_GUZZLE4_HDR */ diff --git a/agent/lib_mongodb.c b/agent/lib_mongodb.c index aa96d1b60..4672187ee 100644 --- a/agent/lib_mongodb.c +++ b/agent/lib_mongodb.c @@ -184,7 +184,11 @@ NR_PHP_WRAPPER(nr_mongodb_operation_before) { nr_segment_t* segment = NULL; segment = nr_segment_start(NRPRG(txn), NULL, NULL); if (NULL != segment) { +#if ZEND_MODULE_API_NO < ZEND_8_2_X_API_NO segment->wraprec = auto_segment->wraprec; +#else + segment->execute_data = auto_segment->execute_data; +#endif } } NR_PHP_WRAPPER_END diff --git a/agent/lib_predis.c b/agent/lib_predis.c index f25b3c505..5d009a9df 100644 --- a/agent/lib_predis.c +++ b/agent/lib_predis.c @@ -761,7 +761,11 @@ NR_PHP_WRAPPER(nr_predis_webdisconnection_executeCommand_before) { nr_segment_t* segment = NULL; segment = nr_segment_start(NRPRG(txn), NULL, NULL); if (NULL != segment) { +#if ZEND_MODULE_API_NO >= ZEND_8_2_X_API_NO + segment->execute_data = auto_segment->execute_data; +#else segment->wraprec = auto_segment->wraprec; +#endif } } NR_PHP_WRAPPER_END diff --git a/agent/php_execute.c b/agent/php_execute.c index ac5121412..cfb9be43f 100644 --- a/agent/php_execute.c +++ b/agent/php_execute.c @@ -1884,9 +1884,11 @@ static void nr_php_observer_attempt_call_cufa_handler(NR_EXECUTE_PROTO) { static void nr_php_instrument_func_begin(NR_EXECUTE_PROTO) { nr_segment_t* segment = NULL; - nruserfn_t* wraprec = NULL; +#if ZEND_MODULE_API_NO < ZEND_8_2_X_API_NO nrtime_t txn_start_time = 0; + nruserfn_t* wraprec = NULL; int zcaught = 0; +#endif NR_UNUSED_FUNC_RETURN_VALUE; if (NULL == NRPRG(txn)) { @@ -1894,7 +1896,9 @@ static void nr_php_instrument_func_begin(NR_EXECUTE_PROTO) { } NRTXNGLOBAL(execute_count) += 1; +#if ZEND_MODULE_API_NO < ZEND_8_2_X_API_NO txn_start_time = nr_txn_start_time(NRPRG(txn)); +#endif /* * Handle here, but be aware the classes might not be loaded yet. */ @@ -1918,15 +1922,21 @@ static void nr_php_instrument_func_begin(NR_EXECUTE_PROTO) { */ nr_php_observer_attempt_call_cufa_handler(NR_EXECUTE_ORIG_ARGS); } +#if ZEND_MODULE_API_NO < ZEND_8_2_X_API_NO wraprec = nr_php_get_wraprec(execute_data->func); +#endif segment = nr_segment_start(NRPRG(txn), NULL, NULL); +#if ZEND_MODULE_API_NO >= ZEND_8_2_X_API_NO + segment->execute_data = execute_data; +#endif if (nrunlikely(NULL == segment)) { nrl_verbosedebug(NRL_AGENT, "Error starting segment."); return; } +#if ZEND_MODULE_API_NO < ZEND_8_2_X_API_NO if (NULL == wraprec) { return; } @@ -1972,17 +1982,83 @@ static void nr_php_instrument_func_begin(NR_EXECUTE_PROTO) { * Check for, and handle, frameworks. */ if (wraprec->is_names_wt_simple) { - nr_txn_name_from_function(NRPRG(txn), wraprec->funcname, - wraprec->classname); + + nr_txn_name_from_function(NRPRG(txn), + nr_php_op_array_function_name(NR_OP_ARRAY), + nr_php_class_entry_name(NR_OP_ARRAY->scope)); + } +#endif +} + +#if ZEND_MODULE_API_NO >= ZEND_8_2_X_API_NO +void nr_php_observer_fcall_begin_late(zend_execute_data* execute_data, nrtime_t txn_start_time, bool name_transaction) { + /* + * During nr_zend_call_oapi_special_before, the transaction may have been + * ended and/or a new transaction may have started. To detect this, we + * compare the currently active transaction's start time with the transaction + * start time we saved before. + * + * Just comparing the transaction pointer is not enough, as a newly + * started transaction might actually obtain the same address as a + * transaction freed before. + */ + (void)execute_data; + if (nrunlikely(nr_txn_start_time(NRPRG(txn)) != txn_start_time)) { + nrl_verbosedebug(NRL_AGENT, + "%s txn ended and/or started while in a wrapped function", + __func__); + + return; } + + /* + * Check for, and handle, frameworks. + */ + if (name_transaction) { + + nr_txn_name_from_function(NRPRG(txn), + nr_php_op_array_function_name(NR_OP_ARRAY), + NR_OP_ARRAY->scope ? + nr_php_class_entry_name(NR_OP_ARRAY->scope) : + NULL); + } +} +#endif + +#if ZEND_MODULE_API_NO >= ZEND_8_2_X_API_NO +void nr_php_observer_fcall_end_late(zend_execute_data* execute_data, bool create_metric, nrtime_t txn_start_time) { + nr_segment_t* segment; + nr_php_execute_metadata_t metadata = {0}; + if (nrunlikely(nr_txn_start_time(NRPRG(txn)) != txn_start_time)) { + nrl_verbosedebug(NRL_AGENT, + "%s txn ended and/or started while in a wrapped function", + __func__); + + return; + } + + /* + * Reassign segment to the current segment, as some before/after wraprecs + * start and then stop a segment. If that happened, we want to ensure we + * get the now-current segment + */ + segment = nr_txn_get_current_segment(NRPRG(txn), NULL); + nr_php_execute_metadata_init(&metadata, NR_OP_ARRAY); + nr_php_execute_segment_end(segment, &metadata, create_metric); + nr_php_execute_metadata_release(&metadata); } +#endif +#if ZEND_MODULE_API_NO < ZEND_8_2_X_API_NO static void nr_php_instrument_func_end(NR_EXECUTE_PROTO) { int zcaught = 0; - nr_segment_t* segment = NULL; nruserfn_t* wraprec = NULL; bool create_metric = false; nr_php_execute_metadata_t metadata = {0}; +#else +static void nr_php_instrument_func_end(NR_EXECUTE_PROTO, bool create_metric, bool end_segment, bool is_exception_handler) { +#endif + nr_segment_t* segment = NULL; nrtime_t txn_start_time = 0; if (NULL == NRPRG(txn)) { @@ -2017,9 +2093,12 @@ static void nr_php_instrument_func_end(NR_EXECUTE_PROTO) { return; } +#if ZEND_MODULE_API_NO < ZEND_8_2_X_API_NO wraprec = segment->wraprec; - if (segment->is_exception_handler) { +#else + if (is_exception_handler) { +#endif /* * After running the exception handler segment, create an error from * the exception it handled, and save the error in the transaction. @@ -2033,7 +2112,11 @@ static void nr_php_instrument_func_end(NR_EXECUTE_PROTO) { nr_php_error_record_exception( NRPRG(txn), exception, nr_php_error_get_priority(E_ERROR), false, "Uncaught exception ", &NRPRG(exception_filters) TSRMLS_CC); +#if ZEND_MODULE_API_NO < ZEND_8_2_X_API_NO } else if (NULL == nr_php_get_return_value(NR_EXECUTE_ORIG_ARGS)) { +#else + } else if (NULL == func_return_value) { +#endif /* * Having no return value (and not being an exception handler) indicates * that this segment had an uncaught exception. We want to add that @@ -2050,7 +2133,6 @@ static void nr_php_instrument_func_end(NR_EXECUTE_PROTO) { __func__); } } - /* * Stop the segment time now so we don't add our additional processing on to * the segment's time. @@ -2062,6 +2144,7 @@ static void nr_php_instrument_func_end(NR_EXECUTE_PROTO) { * has specifically requested it. */ +#if ZEND_MODULE_API_NO < ZEND_8_2_X_API_NO if (NULL != wraprec) { /* * This is the case for specifically requested custom instrumentation. @@ -2108,10 +2191,19 @@ static void nr_php_instrument_func_end(NR_EXECUTE_PROTO) { nr_php_execute_metadata_init(&metadata, NR_OP_ARRAY); nr_php_execute_segment_end(segment, &metadata, create_metric); nr_php_execute_metadata_release(&metadata); +#else + if (end_segment) { + nr_php_observer_fcall_end_late(execute_data, create_metric, txn_start_time); + } +#endif return; } +#if ZEND_MODULE_API_NO >= ZEND_8_2_X_API_NO +static void nr_php_observer_fcall_begin_common(zend_execute_data* execute_data, bool call_late, bool name_transaction) { +#else void nr_php_observer_fcall_begin(zend_execute_data* execute_data) { +#endif /* * Instrument the function. * This and any other needed helper functions will replace: @@ -2120,6 +2212,9 @@ void nr_php_observer_fcall_begin(zend_execute_data* execute_data) { * nr_php_execute_show */ zval* func_return_value = NULL; + //if (execute_data->func && execute_data->func->common.function_name) { + // printf("BEGIN %s\n", ZSTR_VAL(execute_data->func->common.function_name)); + //} if (nrunlikely(NULL == execute_data)) { return; } @@ -2138,15 +2233,34 @@ void nr_php_observer_fcall_begin(zend_execute_data* execute_data) { int show_executes = NR_PHP_PROCESS_GLOBALS(special_flags).show_executes; if (nrunlikely(show_executes)) { + nrl_verbosedebug(NRL_AGENT, + "Stack depth: %d after OAPI function beginning via %s", + NRPRG(php_cur_stack_depth), __func__); nr_php_show_exec(NR_EXECUTE_ORIG_ARGS); } + if (NULL == NRPRG(txn)) { + return; + } nr_php_instrument_func_begin(NR_EXECUTE_ORIG_ARGS); +#if ZEND_MODULE_API_NO >= ZEND_8_2_X_API_NO + if (call_late) { + nr_php_observer_fcall_begin_late(execute_data, nr_txn_start_time(NRPRG(txn)), name_transaction); + } +#endif return; } +#if ZEND_MODULE_API_NO >= ZEND_8_2_X_API_NO +static void nr_php_observer_fcall_end_common(zend_execute_data* execute_data, + zval* func_return_value, + bool create_metric, + bool end_segment, + bool is_exception_handler) { +#else void nr_php_observer_fcall_end(zend_execute_data* execute_data, zval* func_return_value) { +#endif /* * Instrument the function. * This and any other needed helper functions will replace: @@ -2157,16 +2271,26 @@ void nr_php_observer_fcall_end(zend_execute_data* execute_data, if (nrunlikely(NULL == execute_data)) { return; } + //if (execute_data->func && execute_data->func->common.function_name) { + // printf("END %s\n", ZSTR_VAL(execute_data->func->common.function_name)); + //} if (nrlikely(1 == nr_php_recording())) { int show_executes_return = NR_PHP_PROCESS_GLOBALS(special_flags).show_execute_returns; if (nrunlikely(show_executes_return)) { + nrl_verbosedebug(NRL_AGENT, + "Stack depth: %d before OAPI function exiting via %s", + NRPRG(php_cur_stack_depth), __func__); nr_php_show_exec_return(NR_EXECUTE_ORIG_ARGS TSRMLS_CC); } +#if ZEND_MODULE_API_NO >= ZEND_8_2_X_API_NO + nr_php_instrument_func_end(NR_EXECUTE_ORIG_ARGS, create_metric, end_segment, is_exception_handler); +#else nr_php_instrument_func_end(NR_EXECUTE_ORIG_ARGS); +#endif } NRPRG(php_cur_stack_depth) -= 1; @@ -2174,4 +2298,52 @@ void nr_php_observer_fcall_end(zend_execute_data* execute_data, return; } +#if ZEND_MODULE_API_NO >= ZEND_8_2_X_API_NO +void nr_php_observer_fcall_begin(zend_execute_data* execute_data) { + nr_php_observer_fcall_begin_common(execute_data, false, false); +} +void nr_php_observer_fcall_begin_instrumented(zend_execute_data* execute_data) { + nr_php_observer_fcall_begin_common(execute_data, true, false); +} +void nr_php_observer_fcall_begin_name_transaction(zend_execute_data* execute_data) { + nr_php_observer_fcall_begin_common(execute_data, true, true); +} +void nr_php_observer_fcall_end(zend_execute_data* execute_data, + zval* func_return_value) { + nr_php_observer_fcall_end_common(execute_data, func_return_value, false, true, false); +} +void nr_php_observer_fcall_end_create_metric(zend_execute_data* execute_data, + zval* func_return_value) { + nr_php_observer_fcall_end_common(execute_data, func_return_value, true, true, false); +} +void nr_php_observer_fcall_end_keep_segment(zend_execute_data* execute_data, + zval* func_return_value) { + nr_php_observer_fcall_end_common(execute_data, func_return_value, false, false, false); +} +void nr_php_observer_fcall_end_exception_handler(zend_execute_data* execute_data, + zval* func_return_value) { + nr_php_observer_fcall_end_common(execute_data, func_return_value, false, true, true); +} + +// These empty functions (rather than NULL) are used to know if instrumentation +// has been added This is needed because the process for adding instrumentation +// with a transient wrapper differs depending on if the function has been +// previously called. These will only be used when tt_detail is 0. +void nr_php_observer_empty_fcall_begin(zend_execute_data* execute_data) { + (void)execute_data; +} + +void nr_php_observer_empty_fcall_end(zend_execute_data* execute_data, + zval* func_return_value) { + (void)execute_data; + (void)func_return_value; + + /* need a way to register framework info while tt_detail is 0 */ + if (nrunlikely(OP_ARRAY_IS_A_FILE(NR_OP_ARRAY))) { + nr_php_execute_file(NR_OP_ARRAY, NR_EXECUTE_ORIG_ARGS); + } +} + +#endif + #endif diff --git a/agent/php_observer.c b/agent/php_observer.c index 5719be38e..019063cb7 100644 --- a/agent/php_observer.c +++ b/agent/php_observer.c @@ -72,6 +72,7 @@ /* * Register the begin and end function handlers with the Observer API. */ +#if ZEND_MODULE_API_NO < ZEND_8_2_X_API_NO /* PHP8+ */ static zend_observer_fcall_handlers nr_php_fcall_register_handlers( zend_execute_data* execute_data) { zend_observer_fcall_handlers handlers = {NULL, NULL}; @@ -87,6 +88,122 @@ static zend_observer_fcall_handlers nr_php_fcall_register_handlers( return handlers; } +#else + +static zend_observer_fcall_begin_handler nr_php_observer_determine_begin_handler(nruserfn_t* wraprec) { + zend_observer_fcall_begin_handler begin = NULL; + + if (NULL == wraprec) { + if (NRINI(tt_detail)) { + begin = nr_php_observer_fcall_begin; + } else { + begin = nr_php_observer_empty_fcall_begin; + } + } else { + if (wraprec->special_instrumentation_before) { + begin = (zend_observer_fcall_begin_handler)wraprec->special_instrumentation_before; + } else if (wraprec->is_names_wt_simple) { + begin = nr_php_observer_fcall_begin_name_transaction; + } else if (wraprec->is_transient) { + begin = nr_php_observer_fcall_begin; + } else { + begin = nr_php_observer_fcall_begin_instrumented; + } + } + return begin; +} + +static zend_observer_fcall_end_handler nr_php_observer_determine_end_handler(nruserfn_t* wraprec) { + zend_observer_fcall_end_handler end = NULL; + + if (NULL == wraprec) { + if (NRINI(tt_detail)) { + end = nr_php_observer_fcall_end; + } else { + end = nr_php_observer_empty_fcall_end; + } + } else { + if (wraprec->special_instrumentation) { + end = (zend_observer_fcall_end_handler)wraprec->special_instrumentation; + } else if (wraprec->is_exception_handler) { + end = nr_php_observer_fcall_end_exception_handler; + } else if (wraprec->create_metric) { + end = nr_php_observer_fcall_end_create_metric; + } else { + end = nr_php_observer_fcall_end; + } + } + return end; +} + +static zend_observer_fcall_handlers nr_php_fcall_register_handlers( + zend_execute_data* execute_data) { + zend_observer_fcall_handlers handlers = {NULL, NULL}; + nruserfn_t* wraprec = NULL; + if (NULL == execute_data) { + return handlers; + } + if ((NULL == execute_data->func) + || (ZEND_INTERNAL_FUNCTION == execute_data->func->type)) { + return handlers; + } + //if (execute_data->func && execute_data->func->common.function_name) { + // printf("REGISTER %s\n", ZSTR_VAL(execute_data->func->common.function_name)); + //} + wraprec = nr_php_get_wraprec(execute_data->func); + handlers.begin = nr_php_observer_determine_begin_handler(wraprec); + handlers.end = nr_php_observer_determine_end_handler(wraprec); + if (wraprec) { + nr_txn_force_single_count(NRPRG(txn), wraprec->supportability_metric); + } + return handlers; +} + +bool nr_php_observer_is_registered(zend_function* func) { + zend_observer_fcall_begin_handler *begin_handler; + if (func == NULL) { + return false; + } + begin_handler = (zend_observer_fcall_begin_handler *)&ZEND_OP_ARRAY_EXTENSION((&(func)->common), zend_observer_fcall_op_array_extension); + // begin_handler will be NULL if the observer hasn't been installed yet. + // *begin_Handler will be (void*)2 if the function has not yet been called. + return (begin_handler && *begin_handler != (void*)2); +} + +bool nr_php_observer_remove_begin_handler(zend_function* func, nruserfn_t* wraprec) { + if (!nr_php_observer_is_registered(func)) { + return false; + } + zend_observer_fcall_begin_handler begin = nr_php_observer_determine_begin_handler(wraprec);; + return zend_observer_remove_begin_handler(func, begin); +} + +bool nr_php_observer_remove_end_handler(zend_function* func, nruserfn_t* wraprec) { + if (!nr_php_observer_is_registered(func)) { + return false; + } + zend_observer_fcall_end_handler end = nr_php_observer_determine_end_handler(wraprec); + return zend_observer_remove_end_handler(func, end); +} + +void nr_php_observer_add_begin_handler(zend_function* func, nruserfn_t* wraprec) { + if (!nr_php_observer_is_registered(func)) { + return; + } + zend_observer_fcall_begin_handler begin = nr_php_observer_determine_begin_handler(wraprec);; + zend_observer_add_begin_handler(func, begin); +} + +void nr_php_observer_add_end_handler(zend_function* func, nruserfn_t* wraprec) { + if (!nr_php_observer_is_registered(func)) { + return; + } + zend_observer_fcall_end_handler end = nr_php_observer_determine_end_handler(wraprec); + zend_observer_add_end_handler(func, end); +} +#endif + + void nr_php_observer_no_op(zend_execute_data* execute_data NRUNUSED){}; void nr_php_observer_minit() { diff --git a/agent/php_observer.h b/agent/php_observer.h index 6a80873cb..f6069f7bd 100644 --- a/agent/php_observer.h +++ b/agent/php_observer.h @@ -17,6 +17,7 @@ #if ZEND_MODULE_API_NO >= ZEND_8_0_X_API_NO /* PHP8+ */ #include "Zend/zend_observer.h" +#include "php_user_instrument.h" /* * Purpose: There are a few various places, aside from the php_execute_* family @@ -76,6 +77,32 @@ void nr_php_observer_fcall_end(zend_execute_data* execute_data, zval* func_return_value); +#if ZEND_MODULE_API_NO >= ZEND_8_2_X_API_NO +bool nr_php_observer_is_registered(zend_function* func); +bool nr_php_observer_remove_begin_handler(zend_function* func, nruserfn_t* wraprec); +bool nr_php_observer_remove_end_handler(zend_function* func, nruserfn_t* wraprec); +void nr_php_observer_add_begin_handler(zend_function* func, nruserfn_t* wraprec); +void nr_php_observer_add_end_handler(zend_function* func, nruserfn_t* wraprec); + +/* + * These different forms of fcall_begin and fcall_end are needed to properly utilize + * the fields in a wraprec without looking it up every call. +*/ +void nr_php_observer_empty_fcall_begin(zend_execute_data* execute_data); +void nr_php_observer_fcall_begin_instrumented(zend_execute_data* execute_data); +void nr_php_observer_fcall_begin_name_transaction(zend_execute_data* execute_data); + +void nr_php_observer_empty_fcall_end(zend_execute_data* execute_data, + zval* func_return_value); +void nr_php_observer_fcall_begin_late(zend_execute_data* execute_data, nrtime_t txn_start_time, bool name_transaction); +void nr_php_observer_fcall_end_keep_segment(zend_execute_data* execute_data, + zval* func_return_value); +void nr_php_observer_fcall_end_late(zend_execute_data* execute_data, bool create_metric, nrtime_t txn_start_time); +void nr_php_observer_fcall_end_create_metric(zend_execute_data* execute_data, + zval* func_return_value); +void nr_php_observer_fcall_end_exception_handler(zend_execute_data* execute_data, + zval* func_return_value); +#endif /* PHP 8.2+ */ #endif /* PHP8+ */ #endif // NEWRELIC_PHP_AGENT_PHP_OBSERVER_H diff --git a/agent/php_rinit.c b/agent/php_rinit.c index d704f935e..922e4a884 100644 --- a/agent/php_rinit.c +++ b/agent/php_rinit.c @@ -124,6 +124,7 @@ PHP_RINIT_FUNCTION(newrelic) { nr_stack_init(&NRPRG(drupal_invoke_all_hooks), NR_STACK_DEFAULT_CAPACITY); nr_stack_init(&NRPRG(drupal_invoke_all_states), NR_STACK_DEFAULT_CAPACITY); NRPRG(predis_ctxs).dtor = str_stack_dtor; + NRPRG(wordpress_tags).dtor = str_stack_dtor; NRPRG(drupal_invoke_all_hooks).dtor = zval_stack_dtor; #endif diff --git a/agent/php_user_instrument.c b/agent/php_user_instrument.c index c7fab76d0..73047f7c5 100644 --- a/agent/php_user_instrument.c +++ b/agent/php_user_instrument.c @@ -62,6 +62,24 @@ int nr_zend_call_orig_execute(NR_EXECUTE_PROTO TSRMLS_DC) { return zcaught; } #if ZEND_MODULE_API_NO >= ZEND_8_0_X_API_NO /* PHP8+ */ +#if ZEND_MODULE_API_NO >= ZEND_8_2_X_API_NO /* PHP8+ */ +int nr_zend_call_oapi_special_before(nruserfn_t* wraprec, + nr_segment_t* segment, + NR_EXECUTE_PROTO) { + volatile int zcaught = 0; + (void)segment; + + if (wraprec && wraprec->special_instrumentation_before) { + zend_try { + wraprec->special_instrumentation_before(NR_EXECUTE_ORIG_ARGS); + } + zend_catch { zcaught = 1; } + zend_end_try(); + } + + return zcaught; +} +#else int nr_zend_call_oapi_special_before(nruserfn_t* wraprec, nr_segment_t* segment, NR_EXECUTE_PROTO) { @@ -79,15 +97,23 @@ int nr_zend_call_oapi_special_before(nruserfn_t* wraprec, return zcaught; } #endif +#endif int nr_zend_call_orig_execute_special(nruserfn_t* wraprec, nr_segment_t* segment, NR_EXECUTE_PROTO TSRMLS_DC) { +#if ZEND_MODULE_API_NO >= ZEND_8_2_X_API_NO /* PHP8+ */ + (void)segment; +#endif volatile int zcaught = 0; NR_UNUSED_FUNC_RETURN_VALUE; zend_try { if (wraprec && wraprec->special_instrumentation) { +#if ZEND_MODULE_API_NO >= ZEND_8_2_X_API_NO /* PHP8+ */ + wraprec->special_instrumentation(NR_EXECUTE_ORIG_ARGS); +#else wraprec->special_instrumentation(wraprec, segment, NR_EXECUTE_ORIG_ARGS TSRMLS_CC); +#endif } else { NR_PHP_PROCESS_GLOBALS(orig_execute) (NR_EXECUTE_ORIG_ARGS_OVERWRITE TSRMLS_CC); @@ -258,6 +284,9 @@ static void nr_php_wrap_user_function_internal(nruserfn_t* wraprec TSRMLS_DC) { /* It could be in a file not yet loaded, no reason to log anything. */ return; } +#if ZEND_MODULE_API_NO >= ZEND_8_2_X_API_NO + wraprec->func = orig_func; +#endif if (ZEND_USER_FUNCTION != orig_func->type) { nrl_verbosedebug(NRL_INSTRUMENT, "%s%s%s is not a user function", @@ -402,6 +431,10 @@ nruserfn_t* nr_php_add_custom_tracer_callable(zend_function* func TSRMLS_DC) { nrl_verbosedebug(NRL_INSTRUMENT, "reusing custom wrapper for callable '%s'", name); nr_free(name); +#if ZEND_MODULE_API_NO >= ZEND_8_2_X_API_NO + nr_php_observer_remove_begin_handler(func, wraprec); + nr_php_observer_remove_end_handler(func, wraprec); +#endif return wraprec; } @@ -415,6 +448,10 @@ nruserfn_t* nr_php_add_custom_tracer_callable(zend_function* func TSRMLS_DC) { #if ZEND_MODULE_API_NO < ZEND_7_4_X_API_NO nr_php_add_custom_tracer_common(wraprec); #endif +#if ZEND_MODULE_API_NO >= ZEND_8_2_X_API_NO + nr_php_observer_remove_begin_handler(func, NULL); + nr_php_observer_remove_end_handler(func, NULL); +#endif return wraprec; } @@ -442,6 +479,10 @@ nruserfn_t* nr_php_add_custom_tracer_named(const char* namestr, nr_php_user_wraprec_destroy(&wraprec); nr_php_wrap_user_function_internal(p TSRMLS_CC); +#if ZEND_MODULE_API_NO >= ZEND_8_2_X_API_NO + nr_php_observer_remove_begin_handler(p->func, wraprec); + nr_php_observer_remove_end_handler(p->func, wraprec); +#endif return p; /* return the wraprec we are duplicating */ } p = p->next; @@ -458,6 +499,10 @@ nruserfn_t* nr_php_add_custom_tracer_named(const char* namestr, * non-transients to be freed when the linked list is disposed of which is at * module shutdown */ nr_php_add_custom_tracer_common(wraprec); +#if ZEND_MODULE_API_NO >= ZEND_8_2_X_API_NO + nr_php_observer_remove_begin_handler(wraprec->func, NULL); + nr_php_observer_remove_end_handler(wraprec->func, NULL); +#endif return wraprec; /* return the new wraprec */ } @@ -542,6 +587,10 @@ void nr_php_add_transaction_naming_function(const char* namestr, if (NULL != wraprec) { wraprec->is_names_wt_simple = 1; +#if ZEND_MODULE_API_NO >= ZEND_8_2_X_API_NO + nr_php_observer_add_begin_handler(wraprec->func, wraprec); + nr_php_observer_add_end_handler(wraprec->func, wraprec); +#endif } } @@ -551,6 +600,10 @@ void nr_php_add_custom_tracer(const char* namestr, int namestrlen TSRMLS_DC) { if (NULL != wraprec) { wraprec->create_metric = 1; wraprec->is_user_added = 1; +#if ZEND_MODULE_API_NO >= ZEND_8_2_X_API_NO + nr_php_observer_add_begin_handler(wraprec->func, wraprec); + nr_php_observer_add_end_handler(wraprec->func, wraprec); +#endif } } @@ -559,6 +612,10 @@ void nr_php_add_exception_function(zend_function* func TSRMLS_DC) { if (wraprec) { wraprec->is_exception_handler = 1; +#if ZEND_MODULE_API_NO >= ZEND_8_2_X_API_NO + nr_php_observer_add_begin_handler(func, wraprec); + nr_php_observer_add_end_handler(func, wraprec); +#endif } } @@ -614,6 +671,10 @@ void nr_php_user_function_add_declared_callback(const char* namestr, if (wraprec->is_wrapped && callback) { (callback)(TSRMLS_C); } +#if ZEND_MODULE_API_NO >= ZEND_8_2_X_API_NO + nr_php_observer_add_begin_handler(wraprec->func, wraprec); + nr_php_observer_add_end_handler(wraprec->func, wraprec); +#endif } } diff --git a/agent/php_user_instrument.h b/agent/php_user_instrument.h index 15fba7bdf..f42e50cd4 100644 --- a/agent/php_user_instrument.h +++ b/agent/php_user_instrument.h @@ -19,12 +19,17 @@ struct _nruserfn_t; * This is an unused structure that is used to ensure that a bare return won't * compile in a user wrapper. */ +#if ZEND_MODULE_API_NO >= ZEND_8_2_X_API_NO +typedef void (*nrspecialfn_t)(zend_execute_data*, ...); + +#else struct nrspecialfn_return_t { int zcaught; }; typedef struct nrspecialfn_return_t (*nrspecialfn_t)( NR_SPECIALFNPTR_PROTO TSRMLS_DC); +#endif typedef void (*nruserfn_declared_t)(TSRMLS_D); /* @@ -105,6 +110,9 @@ typedef struct _nruserfn_t { #if ZEND_MODULE_API_NO >= ZEND_7_4_X_API_NO char* wordpress_plugin_theme; #endif +#if ZEND_MODULE_API_NO >= ZEND_8_2_X_API_NO + zend_function* func; /* the underlying function that this wraprec wraps */ +#endif } nruserfn_t; extern nruserfn_t* nr_wrapped_user_functions; /* a singly linked list */ diff --git a/agent/php_wrapper.c b/agent/php_wrapper.c index 21fa27f15..3306a7723 100644 --- a/agent/php_wrapper.c +++ b/agent/php_wrapper.c @@ -39,6 +39,10 @@ static void nr_php_wraprec_add_before_after_callbacks( __func__, NRSAFELEN(namelen), NRBLANKSTR(name)); return; } + if (wraprec->special_instrumentation_before == before_callback && + wraprec->special_instrumentation == after_callback) { + return; + } wraprec->special_instrumentation = after_callback; wraprec->special_instrumentation_before = before_callback; @@ -51,10 +55,13 @@ nruserfn_t* nr_php_wrap_user_function_before_after( nrspecialfn_t after_callback) { nruserfn_t* wraprec = nr_php_add_custom_tracer_named(name, namelen); - nr_php_wraprec_add_before_after_callbacks(name, namelen, wraprec, - before_callback, - after_callback); + before_callback, + after_callback); +#if ZEND_MODULE_API_NO >= ZEND_8_2_X_API_NO + nr_php_observer_add_begin_handler(wraprec->func, wraprec); + nr_php_observer_add_end_handler(wraprec->func, wraprec); +#endif return wraprec; } @@ -95,8 +102,12 @@ nruserfn_t* nr_php_wrap_callable_before_after( name = nr_php_function_debug_name(callable); } nr_php_wraprec_add_before_after_callbacks(name, nr_strlen(name), wraprec, - before_callback, - after_callback); + before_callback, + after_callback); +#if ZEND_MODULE_API_NO >= ZEND_8_2_X_API_NO + nr_php_observer_add_begin_handler(callable, wraprec); + nr_php_observer_add_end_handler(callable, wraprec); +#endif if (nrl_should_print(NRL_VERBOSEDEBUG, NRL_INSTRUMENT) && NULL != name) { nr_free(name); } @@ -120,6 +131,10 @@ nruserfn_t* nr_php_wrap_user_function(const char* name, } else { wraprec->special_instrumentation = callback; } +#if ZEND_MODULE_API_NO >= ZEND_8_2_X_API_NO + nr_php_observer_add_begin_handler(wraprec->func, wraprec); + nr_php_observer_add_end_handler(wraprec->func, wraprec); +#endif } return wraprec; @@ -150,6 +165,10 @@ nruserfn_t* nr_php_wrap_callable(zend_function* callable, __func__); } else { wraprec->special_instrumentation = callback; +#if ZEND_MODULE_API_NO >= ZEND_8_2_X_API_NO + nr_php_observer_add_begin_handler(callable, wraprec); + nr_php_observer_add_end_handler(callable, wraprec); +#endif } } diff --git a/agent/php_wrapper.h b/agent/php_wrapper.h index b87a5d9c0..23fa7539c 100644 --- a/agent/php_wrapper.h +++ b/agent/php_wrapper.h @@ -252,9 +252,17 @@ extern void nr_php_scope_release(zval** ppzv); */ extern zval** nr_php_get_return_value_ptr(TSRMLS_D); +#if ZEND_MODULE_API_NO < ZEND_8_2_X_API_NO #define NR_PHP_WRAPPER_PROTOTYPE(name) \ struct nrspecialfn_return_t name(NR_SPECIALFNPTR_PROTO TSRMLS_DC) +#else + +#define NR_PHP_WRAPPER_PROTOTYPE(name) \ + void name(zend_execute_data* execute_data, ...) +#endif + +#if ZEND_MODULE_API_NO < ZEND_8_2_X_API_NO #define NR_PHP_WRAPPER_START(name) \ NR_PHP_WRAPPER_PROTOTYPE(name) { \ int was_executed = 0; \ @@ -264,9 +272,34 @@ extern zval** nr_php_get_return_value_ptr(TSRMLS_D); \ (void)auto_segment; // auto_segment isn't generally used in wrapper // functions +#else +#define NR_PHP_WRAPPER_START(name) \ + NR_PHP_WRAPPER_PROTOTYPE(name) { \ + int was_executed = 0; \ + bool in_begin = true;\ + int zcaught = 0; \ + nruserfn_t* wraprec = NULL; \ + zval* func_return_value = NULL; \ + const nrtxn_t* txn = NRPRG(txn); \ + const nrtime_t txn_start_time = nr_txn_start_time(txn); \ + \ + nr_segment_t* auto_segment = nr_txn_get_current_segment(NRPRG(txn), NULL); \ + if (!auto_segment || auto_segment->execute_data != execute_data) { \ + nr_php_observer_fcall_begin(execute_data); \ + } else { \ + va_list args; \ + va_start(args, execute_data); \ + func_return_value = va_arg(args, zval*); \ + va_end(args); \ + in_begin = false; \ + nr_php_observer_fcall_end_keep_segment(execute_data, \ + func_return_value); \ + } +#endif #define NR_PHP_WRAPPER(name) static NR_PHP_WRAPPER_START(name) +#if ZEND_MODULE_API_NO < ZEND_8_2_X_API_NO #define NR_PHP_WRAPPER_END \ callback_end: \ __attribute__((unused)); \ @@ -281,7 +314,25 @@ extern zval** nr_php_get_return_value_ptr(TSRMLS_D); struct nrspecialfn_return_t _retval = {zcaught}; \ return _retval; \ } \ - } +} +#else +#define NR_PHP_WRAPPER_END \ + callback_end: \ + __attribute__((unused)); \ + if (!was_executed) { \ + NR_PHP_WRAPPER_CALL \ + } \ + if (in_begin) { \ + nr_php_observer_fcall_begin_late(execute_data, txn_start_time, false);\ + } else { \ + nr_php_observer_fcall_end_late(execute_data, false, txn_start_time); \ + } \ + if (zcaught) { \ + zend_bailout(); \ + } \ +} +#endif + #define NR_PHP_WRAPPER_CALL \ if (!was_executed) { \ @@ -326,11 +377,16 @@ extern zval** nr_php_get_return_value_ptr(TSRMLS_D); } \ } while (0) +#if ZEND_MODULE_API_NO < ZEND_8_2_X_API_NO #define NR_PHP_WRAPPER_DELEGATE(name) \ if (!was_executed) { \ zcaught = ((name)(NR_SPECIALFNPTR_ORIG_ARGS TSRMLS_CC)).zcaught; \ was_executed = 1; \ } +#else +#define NR_PHP_WRAPPER_DELEGATE(name) \ + ((name)(execute_data, func_return_value)); +#endif static inline bool is_instrumentation_set_and_not_equal( nrspecialfn_t instrumentation, diff --git a/axiom/nr_segment.h b/axiom/nr_segment.h index 56d972579..70fd2d3c3 100644 --- a/axiom/nr_segment.h +++ b/axiom/nr_segment.h @@ -182,7 +182,10 @@ typedef struct _nr_segment_t { external or datastore segments. */ nr_segment_error_t* error; /* segment error attributes */ -#if ZEND_MODULE_API_NO >= ZEND_8_0_X_API_NO \ +#if ZEND_MODULE_API_NO >= ZEND_8_2_X_API_NO + void* execute_data; + +#elif ZEND_MODULE_API_NO >= ZEND_8_0_X_API_NO \ && !defined OVERWRITE_ZEND_EXECUTE_DATA /* PHP 8.0+ and OAPI */ /* diff --git a/daemon/cmd/integration_runner/main.go b/daemon/cmd/integration_runner/main.go index f1a65a402..6edea1394 100644 --- a/daemon/cmd/integration_runner/main.go +++ b/daemon/cmd/integration_runner/main.go @@ -532,7 +532,7 @@ func runTest(t *integration.Test) { if err != nil { t.Output = body - t.Fatal(fmt.Errorf("error executing skipif: %v", err)) + t.Fatal(fmt.Errorf("error executing skipif: %v %v", err, skipIf)) return } diff --git a/tests/integration/span_events/test_span_events_max_samples_stored1.php b/tests/integration/span_events/test_span_events_max_samples_stored1.php index 93807d478..e6937bab5 100644 --- a/tests/integration/span_events/test_span_events_max_samples_stored1.php +++ b/tests/integration/span_events/test_span_events_max_samples_stored1.php @@ -29,7 +29,7 @@ newrelic_add_custom_tracer('main'); function main() { - usleep(10); + usleep(1); } $sample_size = 10000; diff --git a/tests/integration/span_events/test_span_events_on_dt_on.php b/tests/integration/span_events/test_span_events_on_dt_on.php index e94a4570a..d89c0e65c 100644 --- a/tests/integration/span_events/test_span_events_on_dt_on.php +++ b/tests/integration/span_events/test_span_events_on_dt_on.php @@ -76,6 +76,9 @@ Hello */ +if (version_compare(PHP_VERSION, "7.0", "<")) { + die("skip: CLM for PHP 5 not supported\n"); +} newrelic_add_custom_tracer('main'); function main() {