Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
6ed1863
use ZEND_OP_ARRAY_EXTENSION to store wraprec
lavarou Jan 31, 2025
88485df
verify wraprec belongs to process before using it
lavarou Jan 31, 2025
fd5c35b
don't store wraprec on op_array extension too soon
lavarou Jan 31, 2025
d2680ad
use op_array extension only for PHPs 8.0+
lavarou Jan 31, 2025
083bf39
fix use of op_array extension on PHP 8.4
lavarou Jan 31, 2025
d12eda5
re-use nr_php_show_exec in fcall_init
lavarou Feb 18, 2025
97b3c90
refactor wraprec storage and lookup [wip]
lavarou Mar 11, 2025
223ff67
first round of fixes after initial testing
lavarou Mar 11, 2025
4c9098b
second round of fixes
lavarou Mar 12, 2025
82477ae
copy key to wraprec only if new one was created
lavarou Mar 12, 2025
6be44b9
ensure runtime cache for function is initialized
lavarou Mar 12, 2025
3503ec6
don't forget to destroy transient wraprecs!!!
lavarou Mar 12, 2025
4c04db2
third round of fixes
lavarou Mar 13, 2025
cbbb74f
optimize library/framework detection for PHPs 8.0+
lavarou Mar 14, 2025
3ce9c4d
fourth round of fixes
lavarou Mar 14, 2025
1ee6486
remove nr_php_amqplib_ensure_class
lavarou Mar 14, 2025
998970b
fix unit tests build for PHPs < 8.0
lavarou Apr 1, 2025
952f119
fifth round of fixes
lavarou Apr 2, 2025
9d261da
fix laravel console application instrumentation
lavarou Apr 2, 2025
5131a2d
use `NR_OP_ARRAY` macro when accessing &execute_data->func->op_array
lavarou Apr 15, 2025
49bd6f9
sixth round of fixes
lavarou Apr 18, 2025
10416ff
seventh round of fixes
lavarou Apr 18, 2025
65515a8
Merge branch 'dev' into refactor/optimize-wraprec-lookup
lavarou Apr 21, 2025
7ae0675
ensure zend_function* passed to nr_php_wraprec_lookup_get is never NULL
lavarou Apr 22, 2025
986ba9d
add span events tests for different configurations of tt_details
lavarou Apr 23, 2025
2d812cd
fixup! 986ba9dd36f97694c7c7c11c91b261e9e6221047
lavarou Apr 24, 2025
ba95431
Revert 53d10e0870955520e2c020af1194a7733486932a
lavarou Apr 25, 2025
a724769
Revert 2d4eb21a6b1376481f478544c06999b508c53f00
lavarou Apr 25, 2025
cf07d9a
apply code review feedback and reduce code duplication
lavarou Apr 25, 2025
c494a45
ensure use of ZEND_OP_ARRAY_EXTENSION is allowed
lavarou Apr 25, 2025
09eca03
cleanup nr_php_fcall_register_handlers
lavarou Apr 29, 2025
b887845
add input validation when adding named custom tracer
lavarou Apr 29, 2025
64408b5
improve error handling in user function instrumentation initialization
lavarou Apr 29, 2025
575aa95
fixup! b887845010b262ecccd106da367f46b42e688d2b
lavarou Apr 30, 2025
c9563c2
apply code review feedback and add a NULL check
lavarou May 1, 2025
75e2253
simplify nr_php_show_exec
lavarou May 1, 2025
b457827
explain passing `-1` as `namestrlen` argument of nr_php_add_custom_tr…
lavarou May 1, 2025
d6eee1a
log debug message when reusing wraprec in nr_php_user_instrument_wrap…
lavarou May 1, 2025
55ea3c5
apply code review feedback and always initialize pointers to NULL
lavarou May 2, 2025
3cc2311
use a different mechanism to validate value retrieved from ZEND_OP_AR…
lavarou May 2, 2025
25a2df3
test add_custom_tracer with strange input
lavarou May 2, 2025
04ccbd1
test attribute instrumentation in PHP 8.0+
lavarou May 2, 2025
ac8497c
test trampoline functions to ensure stability
lavarou May 2, 2025
d74562e
skip tests for anonymous class's methods instrumentation in PHPs < 8.0
lavarou May 5, 2025
c883e84
make test deterministic
lavarou May 5, 2025
5383c94
use NRSAFESTR when printing strings that can be NULL
lavarou May 5, 2025
8abf1eb
simplify user instrumentation initialization handling
lavarou May 5, 2025
c6c50d9
apply code review feedback and remove redundant NULL checks
lavarou May 8, 2025
9748cf5
add add_custom_tracer_named test for unscoped global function
lavarou May 8, 2025
da65028
correct description in trampoline tests to improve clarity
lavarou May 8, 2025
952b2bc
NRPRG(pid) is only needed for PHPs < 7.4
lavarou May 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions agent/Makefile.frag
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ TEST_BINARIES = \
tests/test_txn_private \
tests/test_user_instrument \
tests/test_user_instrument_hashmap \
tests/test_user_instrument_wraprec_hashmap \
tests/test_zval

.PHONY: unit-tests
Expand Down
1 change: 1 addition & 0 deletions agent/config.m4
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ if test "$PHP_NEWRELIC" = "yes"; then
php_pdo_mysql.c php_pdo_pgsql.c php_pgsql.c php_psr7.c php_redis.c \
php_rinit.c php_rshutdown.c php_samplers.c php_stack.c \
php_stacked_segment.c php_txn.c php_user_instrument.c \
php_user_instrument_wraprec_hashmap.c \
php_user_instrument_hashmap.c php_vm.c php_wrapper.c"
FRAMEWORKS="fw_cakephp.c fw_codeigniter.c fw_drupal8.c \
fw_drupal.c fw_drupal_common.c fw_joomla.c fw_kohana.c \
Expand Down
7 changes: 5 additions & 2 deletions agent/fw_drupal8.c
Original file line number Diff line number Diff line change
Expand Up @@ -140,14 +140,17 @@ static void nr_drupal8_add_method_callback_before_after_clean(
nrspecialfn_t after_callback,
nrspecialfn_t clean_callback) {
zend_function* function = NULL;
char* methodLC = NULL;

if (NULL == ce) {
nrl_verbosedebug(NRL_FRAMEWORK, "Drupal 8: got NULL class entry in %s",
__func__);
return;
}

function = nr_php_find_class_method(ce, method);
methodLC = nr_string_to_lowercase(method);
function = nr_php_find_class_method(ce, methodLC);
nr_free(methodLC);
if (NULL == function) {
nrl_verbosedebug(NRL_FRAMEWORK,
"Drupal 8+: cannot get zend_function entry for %.*s::%.*s",
Expand Down Expand Up @@ -640,7 +643,7 @@ NR_PHP_WRAPPER(nr_drupal8_module_handler) {
#if ZEND_MODULE_API_NO >= ZEND_8_0_X_API_NO \
&& !defined OVERWRITE_ZEND_EXECUTE_DATA
nr_drupal8_add_method_callback_before_after_clean(
ce, NR_PSTR("invokeallwith"), nr_drupal94_invoke_all_with,
ce, NR_PSTR("invokeAllWith"), nr_drupal94_invoke_all_with,
nr_drupal94_invoke_all_with_after, nr_drupal94_invoke_all_with_clean);
#else
nr_drupal8_add_method_callback(ce, NR_PSTR("invokeallwith"),
Expand Down
2 changes: 1 addition & 1 deletion agent/fw_laminas3.c
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ void nr_laminas3_enable(TSRMLS_D) {
*/

nr_php_wrap_user_function(
NR_PSTR("Laminas\\Router\\HTTP\\RouteMatch::setMatchedRouteName"),
NR_PSTR("Laminas\\Router\\Http\\RouteMatch::setMatchedRouteName"),
nr_laminas3_name_the_wt TSRMLS_CC);
nr_php_wrap_user_function(
NR_PSTR("Laminas\\Router\\RouteMatch::setMatchedRouteName"),
Expand Down
4 changes: 2 additions & 2 deletions agent/fw_laravel.c
Original file line number Diff line number Diff line change
Expand Up @@ -1232,10 +1232,10 @@ void nr_laravel_enable(TSRMLS_D) {
#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("Illuminate\\Console\\Application::doRun"),
NR_PSTR("Symfony\\Component\\Console\\Application::doRun"),
nr_laravel_console_application_dorun, NULL, NULL);
#else
nr_php_wrap_user_function(NR_PSTR("Illuminate\\Console\\Application::doRun"),
nr_php_wrap_user_function(NR_PSTR("Symfony\\Component\\Console\\Application::doRun"),
nr_laravel_console_application_dorun TSRMLS_CC);
#endif
/*
Expand Down
24 changes: 0 additions & 24 deletions agent/lib_php_amqplib.c
Original file line number Diff line number Diff line change
Expand Up @@ -81,29 +81,6 @@
* needed to work with MQ_BROKER.
*/

/*
* Purpose : Ensures the php-amqplib instrumentation gets wrapped.
*
* Params : None
*
* Returns : None
*/
static void nr_php_amqplib_ensure_class() {
int result = FAILURE;
zend_class_entry* class_entry = NULL;

class_entry = nr_php_find_class("phpamqplib\\channel\\amqpchannel");
if (NULL == class_entry) {
result = zend_eval_stringl(
NR_PSTR("class_exists('PhpAmqpLib\\Channel\\AMQPChannel');"), NULL,
"nr_php_amqplib_class_exists_channel_amqpchannel");
}
/*
* We don't need to check anything else at this point. If this fails, there's
* nothing else we can do anyway.
*/
}

/*
* Version information will be pulled from PhpAmqpLib\\Package::VERSION
* nr_php_amqplib_handle_version will automatically load the class if it isn't
Expand Down Expand Up @@ -810,7 +787,6 @@ void nr_php_amqplib_enable() {

/* Extract the version */
nr_php_amqplib_handle_version();
nr_php_amqplib_ensure_class();

#if ZEND_MODULE_API_NO >= ZEND_8_0_X_API_NO /* less than PHP8.0 */
nr_php_wrap_user_function_before_after_clean(
Expand Down
47 changes: 17 additions & 30 deletions agent/php_execute.c
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,6 @@

static void nr_php_show_exec_return(NR_EXECUTE_PROTO TSRMLS_DC);
static int nr_php_show_exec_indentation(TSRMLS_D);
static void nr_php_show_exec(NR_EXECUTE_PROTO TSRMLS_DC);

/*
* Purpose: Enable monitoring on specific functions in the framework.
Expand Down Expand Up @@ -594,11 +593,11 @@ static int nr_php_show_exec_indentation(TSRMLS_D) {
* Note that this function doesn't handle internal functions, and will crash if
* you give it one.
*/
static void nr_php_show_exec(NR_EXECUTE_PROTO TSRMLS_DC) {
void nr_php_show_exec(const char* context, NR_EXECUTE_PROTO TSRMLS_DC) {
char argstr[NR_EXECUTE_DEBUG_STRBUFSZ];
const char* filename = nr_php_op_array_file_name(NR_OP_ARRAY);
const char* function_name = nr_php_op_array_function_name(NR_OP_ARRAY);

const char* ctx = context ? context : "execute";
argstr[0] = '\0';

if (NR_OP_ARRAY->scope) {
Expand All @@ -608,12 +607,13 @@ static void nr_php_show_exec(NR_EXECUTE_PROTO TSRMLS_DC) {
nr_show_execute_params(NR_EXECUTE_ORIG_ARGS, argstr TSRMLS_CC);
nrl_verbosedebug(
NRL_AGENT,
"execute: %.*s scope={%.*s} function={" NRP_FMT_UQ
NRP_FMT_UQ ": %.*s scope={%.*s} function={" NRP_FMT_UQ
"}"
" params={" NRP_FMT_UQ
"}"
" %.5s"
"@ " NRP_FMT_UQ ":%d",
NRP_SHOW_EXEC_CONTEXT(ctx),
nr_php_show_exec_indentation(TSRMLS_C), nr_php_indentation_spaces,
NRSAFELEN(nr_php_class_entry_name_length(NR_OP_ARRAY->scope)),
nr_php_class_entry_name(NR_OP_ARRAY->scope),
Expand All @@ -631,12 +631,13 @@ static void nr_php_show_exec(NR_EXECUTE_PROTO TSRMLS_DC) {
nr_show_execute_params(NR_EXECUTE_ORIG_ARGS, argstr TSRMLS_CC);
nrl_verbosedebug(
NRL_AGENT,
"execute: %.*s function={" NRP_FMT_UQ
NRP_FMT_UQ ": %.*s function={" NRP_FMT_UQ
"}"
" params={" NRP_FMT_UQ
"}"
" %.5s"
"@ " NRP_FMT_UQ ":%d",
NRP_SHOW_EXEC_CONTEXT(ctx),
nr_php_show_exec_indentation(TSRMLS_C), nr_php_indentation_spaces,
NRP_PHP(function_name), NRP_ARGSTR(argstr),
#if ZEND_MODULE_API_NO < ZEND_7_4_X_API_NO
Expand All @@ -649,16 +650,17 @@ static void nr_php_show_exec(NR_EXECUTE_PROTO TSRMLS_DC) {
/*
* file
*/
nrl_verbosedebug(NRL_AGENT, "execute: %.*s file={" NRP_FMT "}",
nrl_verbosedebug(NRL_AGENT, NRP_FMT_UQ ": %.*s file={" NRP_FMT "}",
NRP_SHOW_EXEC_CONTEXT(ctx),
nr_php_show_exec_indentation(TSRMLS_C),
nr_php_indentation_spaces, NRP_FILENAME(filename));
} else {
/*
* unknown
*/
nrl_verbosedebug(NRL_AGENT, "execute: %.*s ?",
nr_php_show_exec_indentation(TSRMLS_C),
nr_php_indentation_spaces);
nrl_verbosedebug(NRL_AGENT, NRP_FMT_UQ ": %.*s ?",
NRP_SHOW_EXEC_CONTEXT(ctx),
nr_php_show_exec_indentation(TSRMLS_C), nr_php_indentation_spaces);
}
}

Expand Down Expand Up @@ -982,7 +984,7 @@ static void nr_php_user_instrumentation_from_file(const char* filename,
*/
#define METRIC_NAME_MAX_LEN 512

static void nr_php_execute_file(const zend_op_array* op_array,
void nr_php_execute_file(const zend_op_array* op_array,
NR_EXECUTE_PROTO TSRMLS_DC) {
const char* filename = nr_php_op_array_file_name(op_array);
size_t filename_len = nr_php_op_array_file_name_len(op_array);
Expand All @@ -1007,7 +1009,9 @@ static void nr_php_execute_file(const zend_op_array* op_array,
return;
}

#if ZEND_MODULE_API_NO < ZEND_8_0_X_API_NO
nr_php_add_user_instrumentation(TSRMLS_C);
#endif
}

/*
Expand Down Expand Up @@ -1519,7 +1523,7 @@ static void nr_php_execute_enabled(NR_EXECUTE_PROTO TSRMLS_DC) {

static void nr_php_execute_show(NR_EXECUTE_PROTO TSRMLS_DC) {
if (nrunlikely(NR_PHP_PROCESS_GLOBALS(special_flags).show_executes)) {
nr_php_show_exec(NR_EXECUTE_ORIG_ARGS TSRMLS_CC);
nr_php_show_exec("execute", NR_EXECUTE_ORIG_ARGS TSRMLS_CC);
}

nr_php_execute_enabled(NR_EXECUTE_ORIG_ARGS TSRMLS_CC);
Expand Down Expand Up @@ -1905,16 +1909,7 @@ static void nr_php_instrument_func_begin(NR_EXECUTE_PROTO) {

NRTXNGLOBAL(execute_count) += 1;
txn_start_time = nr_txn_start_time(NRPRG(txn));
/*
* Handle here, but be aware the classes might not be loaded yet.
*/
if (nrunlikely(OP_ARRAY_IS_A_FILE(NR_OP_ARRAY))) {
const char* filename = nr_php_op_array_file_name(NR_OP_ARRAY);
size_t filename_len = nr_php_op_array_file_name_len(NR_OP_ARRAY);
nr_execute_handle_framework(all_frameworks, num_all_frameworks,
filename, filename_len TSRMLS_CC);
return;
}

if (NULL != NRPRG(cufa_callback) && NRPRG(check_cufa)) {
/*
* For PHP 7+, call_user_func_array() is flattened into an inline by
Expand Down Expand Up @@ -2000,14 +1995,6 @@ static void nr_php_instrument_func_end(NR_EXECUTE_PROTO) {
}
txn_start_time = nr_txn_start_time(NRPRG(txn));

/*
* Let's get the framework info.
*/
if (nrunlikely(OP_ARRAY_IS_A_FILE(NR_OP_ARRAY))) {
nr_php_execute_file(NR_OP_ARRAY, NR_EXECUTE_ORIG_ARGS TSRMLS_CC);
return;
}

/*
* Get the current segment and return if null.
*/
Expand Down Expand Up @@ -2157,7 +2144,7 @@ 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)) {
nr_php_show_exec(NR_EXECUTE_ORIG_ARGS);
nr_php_show_exec("execute", NR_EXECUTE_ORIG_ARGS);
}
nr_php_instrument_func_begin(NR_EXECUTE_ORIG_ARGS);

Expand Down
15 changes: 15 additions & 0 deletions agent/php_execute.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,21 @@
#define OP_ARRAY_IS_METHOD(OP, FNAME) \
(0 == nr_strcmp(nr_php_op_array_function_name(OP), (FNAME)))

extern void nr_php_execute_file(const zend_op_array* op_array,
NR_EXECUTE_PROTO TSRMLS_DC);

/*
* Purpose: Log information about the execute data in a given execution
* context - either 'execute' (zend_execute) or 'observe' (fcall_init).
* Only first 8 characters of the context are printed.
*
* Caveat: This function doesn't handle internal functions, and will crash if
* you give it one.
*/
extern void nr_php_show_exec(const char*, NR_EXECUTE_PROTO TSRMLS_DC);
/* Limit length of execution context printed in the log file to 8 characters */
#define NRP_SHOW_EXEC_CONTEXT(C) 8, NRSAFESTR(C)

/*
* Purpose: Look through the PHP symbol table for special names or symbols
* that provide additional hints that a specific framework has been loaded.
Expand Down
6 changes: 3 additions & 3 deletions agent/php_globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,11 @@ typedef struct _nrphpglobals_t {
* variable with the key `NEW_RELIC_LABELS` */
#if ZEND_MODULE_API_NO >= ZEND_8_1_X_API_NO /* PHP 8.1+ */
zend_long zend_offset; /* Zend extension offset */
zend_long
zend_op_array_offset; /* Zend extension op_array to modify reserved */
#else
int zend_offset; /* Zend extension offset */
int zend_op_array_offset; /* Zend extension op_array to modify reserved */
#endif
#if ZEND_MODULE_API_NO >= ZEND_8_0_X_API_NO /* PHP 8.0+ */
int op_array_extension_handle; /* Zend op_array extension handle to attach agent's data to function */
#endif
int done_instrumentation; /* Set to true if we have installed instrumentation
handlers */
Expand Down
21 changes: 20 additions & 1 deletion agent/php_minit.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "php_internal_instrument.h"
#include "php_samplers.h"
#include "php_user_instrument.h"
#include "php_user_instrument_wraprec_hashmap.h"
#include "php_vm.h"
#include "php_wrapper.h"
#include "fw_laravel.h"
Expand Down Expand Up @@ -491,6 +492,16 @@ PHP_MINIT_FUNCTION(newrelic) {
*/
nr_php_generate_internal_wrap_records();

#if ZEND_MODULE_API_NO >= ZEND_8_0_X_API_NO
/*
* The user function wraprec hashmap must be initialized before INI processing
* because INI processing adds wraprecs:
* - newrelic.webtransaction.name.functions
* - newrelic.transaction_tracer.custom
*/
nr_php_user_instrument_wraprec_hashmap_init();
#endif

nr_php_register_ini_entries(module_number TSRMLS_CC);

if (0 == NR_PHP_PROCESS_GLOBALS(enabled)) {
Expand Down Expand Up @@ -720,8 +731,16 @@ PHP_MINIT_FUNCTION(newrelic) {
nr_php_set_opcode_handlers();

nrl_debug(NRL_INIT, "MINIT processing done");
#if ZEND_MODULE_API_NO >= ZEND_8_0_X_API_NO /* PHP 7.4+ */
#if ZEND_MODULE_API_NO >= ZEND_8_0_X_API_NO /* PHP 8.0+ */
NR_PHP_PROCESS_GLOBALS(zend_offset) = zend_get_resource_handle(dummy);
NR_PHP_PROCESS_GLOBALS(op_array_extension_handle) = zend_get_op_array_extension_handle("newrelic");
#if ZEND_MODULE_API_NO >= ZEND_8_4_X_API_NO /* PHP 8.4+ */
/* When observer API is used by an extension, both handles (for user
* and internal functions) must be initialized, even when one of them
* is not used (as in our case). Observer API was changed in PHP 8.4.
* For more details see: https://github.com/php/php-src/pull/14252 */
(void) zend_get_internal_function_extension_handle("newrelic");
#endif
#else
NR_PHP_PROCESS_GLOBALS(zend_offset) = zend_get_resource_handle(&dummy);
#endif
Expand Down
33 changes: 33 additions & 0 deletions agent/php_observer.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "php_observer.h"
#include "php_samplers.h"
#include "php_user_instrument.h"
#include "php_user_instrument_wraprec_hashmap.h"
#include "php_vm.h"
#include "php_wrapper.h"
#include "fw_laravel.h"
Expand Down Expand Up @@ -82,6 +83,38 @@ static zend_observer_fcall_handlers nr_php_fcall_register_handlers(
|| (ZEND_INTERNAL_FUNCTION == execute_data->func->type)) {
return handlers;
}

if (nrunlikely(NR_PHP_PROCESS_GLOBALS(special_flags).show_executes)) {
nr_php_show_exec("observe", execute_data, NULL);
}

if (OP_ARRAY_IS_A_FILE(NR_OP_ARRAY)) {
/*
* Let's get the framework info.
*/
nr_php_execute_file(NR_OP_ARRAY, execute_data, NULL TSRMLS_CC);
return handlers;
}

// The function cache slots are not available if the function is a trampoline
if (execute_data->func->op_array.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE) {
if (nrl_should_print(NRL_VERBOSEDEBUG, NRL_INSTRUMENT)) {
char* name = nr_php_function_debug_name(execute_data->func);
nrl_verbosedebug(NRL_INSTRUMENT, "%s - %s is a trampoline function",
__func__, NRSAFESTR(name));
nr_free(name);
}
return handlers;
}

if (!ZEND_OP_ARRAY_EXTENSION(NR_OP_ARRAY, NR_PHP_PROCESS_GLOBALS(op_array_extension_handle))) {
zend_string* func_name = NR_OP_ARRAY->function_name;
zend_string* scope_name = OP_ARRAY_IS_A_METHOD(NR_OP_ARRAY)? NR_OP_ARRAY->scope->name : NULL;
nruserfn_t* wr = nr_php_user_instrument_wraprec_hashmap_get(func_name, scope_name);
// store the wraprec in the op_array extension for the duration of the request for later lookup
ZEND_OP_ARRAY_EXTENSION(NR_OP_ARRAY, NR_PHP_PROCESS_GLOBALS(op_array_extension_handle)) = wr;
}

handlers.begin = nr_php_observer_fcall_begin;
handlers.end = nr_php_observer_fcall_end;
return handlers;
Expand Down
Loading