Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

agent performance improvements #810

Merged
merged 6 commits into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
128 changes: 120 additions & 8 deletions agent/fw_wordpress.c
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,21 @@ static void free_wordpress_metadata(void* metadata) {
nr_free(metadata);
}

static inline void nr_wordpress_hooks_create_metric(nr_segment_t* segment,
const char* hook_name) {
if (NULL == segment) {
return;
}
// need to capture 'now' because segment has not finished yet and therefore
// can't use segment->stop_time
nrtime_t now = nr_txn_now_rel(segment->txn);
nrtime_t duration = nr_time_duration(segment->start_time, now);
if (duration >= NRINI(wordpress_hooks_threshold)) {
// only create metrics for hooks above threshold
nr_wordpress_create_metric(segment, NR_WORDPRESS_HOOK_PREFIX, hook_name);
}
}

static char* nr_wordpress_plugin_from_function(zend_function* func TSRMLS_DC) {
const char* filename = NULL;
size_t filename_len;
Expand Down Expand Up @@ -296,7 +311,9 @@ static char* nr_wordpress_plugin_from_function(zend_function* func TSRMLS_DC) {
}

NR_PHP_WRAPPER(nr_wordpress_wrap_hook) {
#if ZEND_MODULE_API_NO < ZEND_7_4_X_API_NO
zend_function* func = NULL;
#endif
char* plugin = NULL;

NR_UNUSED_SPECIALFN;
Expand All @@ -312,20 +329,27 @@ NR_PHP_WRAPPER(nr_wordpress_wrap_hook) {
if ((0 == NRINI(wordpress_hooks)) || (NULL == NRPRG(wordpress_tag))) {
NR_PHP_WRAPPER_LEAVE;
}
#if ZEND_MODULE_API_NO < ZEND_7_4_X_API_NO
func = nr_php_execute_function(NR_EXECUTE_ORIG_ARGS TSRMLS_CC);
plugin = nr_wordpress_plugin_from_function(func TSRMLS_CC);
#else
plugin = wraprec->wordpress_plugin_theme;
#endif

NR_PHP_WRAPPER_CALL;

nr_wordpress_create_metric(auto_segment, NR_WORDPRESS_HOOK_PREFIX,
NRPRG(wordpress_tag));
nr_wordpress_create_metric(auto_segment, NR_WORDPRESS_PLUGIN_PREFIX, plugin);
if (NULL != plugin || NRPRG(wordpress_core)) {
nr_wordpress_create_metric(auto_segment, NR_WORDPRESS_HOOK_PREFIX,
NRPRG(wordpress_tag));
nr_wordpress_create_metric(auto_segment, NR_WORDPRESS_PLUGIN_PREFIX,
plugin);
}
}
NR_PHP_WRAPPER_END

/*
* A call_user_func_array() callback to ensure that we wrap each hook function.
* PHP 7.3 and below uses old-style wraprec, and will use old style cufa calls.
*/
#if ZEND_MODULE_API_NO < ZEND_7_4_X_API_NO
static void nr_wordpress_call_user_func_array(zend_function* func,
const zend_function* caller
NRUNUSED TSRMLS_DC) {
Expand Down Expand Up @@ -357,6 +381,7 @@ static void nr_wordpress_call_user_func_array(zend_function* func,
*/
nr_php_wrap_callable(func, nr_wordpress_wrap_hook TSRMLS_CC);
}
#endif /* PHP < 7.4 */

static void free_tag(void* tag) {
nr_free(tag);
Expand Down Expand Up @@ -444,6 +469,9 @@ NR_PHP_WRAPPER(nr_wordpress_exec_handle_tag) {

NRPRG(wordpress_tag) = nr_wordpress_clean_tag(tag);
NR_PHP_WRAPPER_CALL;
if (0 == NRPRG(wordpress_plugins)) {
nr_wordpress_hooks_create_metric(auto_segment, NRPRG(wordpress_tag));
}
NRPRG(wordpress_tag) = old_tag;
if (NULL == NRPRG(wordpress_tag)) {
NRPRG(check_cufa) = false;
Expand Down Expand Up @@ -533,6 +561,9 @@ NR_PHP_WRAPPER(nr_wordpress_apply_filters) {
NRPRG(wordpress_tag) = nr_wordpress_clean_tag(tag);

NR_PHP_WRAPPER_CALL;
if (0 == NRPRG(wordpress_plugins)) {
nr_wordpress_hooks_create_metric(auto_segment, NRPRG(wordpress_tag));
}
NRPRG(wordpress_tag) = old_tag;
if (NULL == NRPRG(wordpress_tag)) {
NRPRG(check_cufa) = false;
Expand All @@ -551,6 +582,81 @@ NR_PHP_WRAPPER(nr_wordpress_apply_filters) {
}
NR_PHP_WRAPPER_END

#if ZEND_MODULE_API_NO >= ZEND_7_4_X_API_NO
/*
* Wrap the wordpress function add_filter
*
* function add_filter( $hook_name, $callback, $priority = 10, $accepted_args = 1 )
*
* @param string $hook_name The name of the filter to add the callback to.
* @param callable $callback The callback to be run when the filter is applied.
* @param int $priority Optional. Used to specify the order in which the functions
* associated with a particular filter are executed.
* Lower numbers correspond with earlier execution,
* and functions with the same priority are executed
* in the order in which they were added to the filter. Default 10.
* @param int $accepted_args Optional. The number of arguments the function accepts. Default 1.
* @return true Always returns true.
*/

NR_PHP_WRAPPER(nr_wordpress_add_filter) {
/* Wordpress's add_action() is just a wrapper around add_filter(),
* so we only need to instrument this function */
NR_UNUSED_SPECIALFN;
(void)wraprec;
const char* filename = NULL;
bool wrap_hook = true;

NR_PHP_WRAPPER_REQUIRE_FRAMEWORK(NR_FW_WORDPRESS);

/*
* 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).
*/
if ((NR_FW_WORDPRESS != NRPRG(current_framework))
|| (0 == NRINI(wordpress_hooks))) {
wrap_hook = false;
}

if (NRINI(wordpress_hooks_skip_filename)
&& 0 != nr_strlen(NRINI(wordpress_hooks_skip_filename))) {
filename = nr_php_op_array_file_name(NR_OP_ARRAY);

if (nr_strstr(filename, NRINI(wordpress_hooks_skip_filename))) {
nrl_verbosedebug(NRL_FRAMEWORK, "skipping hooks for function from %s",
filename);
wrap_hook = false;
}
}

if (true == wrap_hook) {
nruserfn_t* callback_wraprec;
zval* callback = nr_php_arg_get(2, NR_EXECUTE_ORIG_ARGS);
zend_function* zf = nr_php_zval_to_function(callback);
if (NULL != zf) {
char* wordpress_plugin_theme = nr_wordpress_plugin_from_function(zf);
if (NULL != wordpress_plugin_theme || NRPRG(wordpress_core)) {
callback_wraprec = nr_php_wrap_callable(zf, nr_wordpress_wrap_hook);
// We can cheat here: wraprecs on callables are always transient, so if
// there's a wordpress_plugin_theme set we know it's from this
// transaction, and we don't have any issues around a possible
// multi-tenant setup.
if (NULL != callback_wraprec
&& NULL == callback_wraprec->wordpress_plugin_theme) {
// Unlike Drupal, we don't free the wordpress_plugin_theme, since we
// know it's transient anyway, and we only set the field if it was
// previously NULL.
callback_wraprec->wordpress_plugin_theme = wordpress_plugin_theme;
}
}
}
nr_php_arg_release(&callback);
}
}
NR_PHP_WRAPPER_END
#endif /* PHP 7.4+ */

void nr_wordpress_enable(TSRMLS_D) {
nr_php_wrap_user_function(NR_PSTR("apply_filters"),
nr_wordpress_apply_filters TSRMLS_CC);
Expand All @@ -564,9 +670,15 @@ void nr_wordpress_enable(TSRMLS_D) {

nr_php_wrap_user_function(NR_PSTR("do_action_ref_array"),
nr_wordpress_exec_handle_tag TSRMLS_CC);

nr_php_add_call_user_func_array_pre_callback(
nr_wordpress_call_user_func_array TSRMLS_CC);
if (0 != NRPRG(wordpress_plugins)) {
#if ZEND_MODULE_API_NO < ZEND_7_4_X_API_NO
nr_php_add_call_user_func_array_pre_callback(
nr_wordpress_call_user_func_array TSRMLS_CC);
#else
nr_php_wrap_user_function(NR_PSTR("add_filter"),
nr_wordpress_add_filter);
#endif
}
}
}

Expand Down
10 changes: 5 additions & 5 deletions agent/php_execute.c
Original file line number Diff line number Diff line change
Expand Up @@ -124,11 +124,11 @@ typedef void (*nr_library_enable_fn_t)(TSRMLS_D);
avail = avail - 3; \
}

static int nr_format_zval_for_debug(zval* arg,
char* pbuf,
size_t pos,
size_t avail,
size_t depth TSRMLS_DC) {
int nr_format_zval_for_debug(zval* arg,
char* pbuf,
size_t pos,
size_t avail,
size_t depth TSRMLS_DC) {
nr_string_len_t len;
nr_string_len_t i;
char* str;
Expand Down
8 changes: 8 additions & 0 deletions agent/php_newrelic.h
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,14 @@ nrinistr_t browser_monitoring_loader; /* newrelic.browser_monitoring.loader */

nrinibool_t drupal_modules; /* newrelic.framework.drupal.modules */
nrinibool_t wordpress_hooks; /* newrelic.framework.wordpress.hooks */
nrinistr_t
wordpress_hooks_options; /* newrelic.framework.wordpress.hooks.options */
nrinitime_t wordpress_hooks_threshold; /* newrelic.framework.wordpress.hooks.threshold
*/
bool wordpress_plugins; /* set based on
newrelic.framework.wordpress.hooks.options */
bool wordpress_core; /* set based on
newrelic.framework.wordpress.hooks.options */
nrinistr_t
wordpress_hooks_skip_filename; /* newrelic.framework.wordpress.hooks_skip_filename
*/
Expand Down
50 changes: 50 additions & 0 deletions agent/php_nrini.c
Original file line number Diff line number Diff line change
Expand Up @@ -1891,6 +1891,40 @@ static PHP_INI_MH(nr_custom_events_max_samples_stored_mh) {
return SUCCESS;
}

#define DEFAULT_WORDPRESS_HOOKS_OPTIONS "all_callbacks"
static PHP_INI_MH(nr_wordpress_hooks_options_mh) {
nrinistr_t* p;

char* base = (char*)mh_arg2;

p = (nrinistr_t*)(base + (size_t)mh_arg1);

(void)mh_arg3;
NR_UNUSED_TSRMLS;

if (0 == nr_strcmp(NEW_VALUE, "all_callbacks")) {
NRPRG(wordpress_plugins) = true;
NRPRG(wordpress_core) = true;
} else if (0 == nr_strcmp(NEW_VALUE, "plugin_callbacks")) {
NRPRG(wordpress_plugins) = true;
NRPRG(wordpress_core) = false;
} else if (0 == nr_strcmp(NEW_VALUE, "threshold")) {
NRPRG(wordpress_plugins) = false;
NRPRG(wordpress_core) = false;
} 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->value = NEW_VALUE;
p->where = stage;

return SUCCESS;
}

/*
* Now for the actual INI entry table. Please note there are two types of INI
* entry specification used.
Expand Down Expand Up @@ -2206,6 +2240,22 @@ STD_PHP_INI_ENTRY_EX("newrelic.framework.wordpress.hooks",
zend_newrelic_globals,
newrelic_globals,
nr_on_off_dh)
STD_PHP_INI_ENTRY_EX("newrelic.framework.wordpress.hooks.options",
DEFAULT_WORDPRESS_HOOKS_OPTIONS,
NR_PHP_REQUEST,
nr_wordpress_hooks_options_mh,
wordpress_hooks_options,
zend_newrelic_globals,
newrelic_globals,
0)
STD_PHP_INI_ENTRY_EX("newrelic.framework.wordpress.hooks.threshold",
"1ms",
NR_PHP_REQUEST,
nr_time_mh,
wordpress_hooks_threshold,
zend_newrelic_globals,
newrelic_globals,
0)
STD_PHP_INI_ENTRY_EX("newrelic.framework.wordpress.hooks_skip_filename",
"",
NR_PHP_REQUEST,
Expand Down
36 changes: 13 additions & 23 deletions agent/php_user_instrument.c
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ static void nr_php_user_wraprec_destroy(nruserfn_t** wraprec_ptr);
static void reset_wraprec(nruserfn_t* wraprec) {
nruserfn_t* p = wraprec;
nr_php_wraprec_hashmap_key_release(&p->key);
if (p->transience == NR_WRAPREC_IS_TRANSIENT) {
if (p->is_transient) {
nr_php_user_wraprec_destroy((nruserfn_t**)&wraprec);
} else {
p->is_wrapped = 0;
Expand Down Expand Up @@ -176,7 +176,7 @@ static void reset_wraprec(nruserfn_t* wraprec) {
* with framework specific instrumentation. Optionally specifies transience.
5) from function `nr_php_wrap_callable` (in `php_wrapper.c`) used only by
* Wordpress and predis for custom instrumentation that sets
* `NR_WRAPREC_IS_TRANSIENT`.
* `is_transient`.
*
* Transient wrappers get disposed of at the end of each request at RSHUTDOWN lifecycle.
*
Expand Down Expand Up @@ -383,7 +383,7 @@ nruserfn_t* nr_php_add_custom_tracer_callable(zend_function* func TSRMLS_DC) {
}

wraprec = nr_php_user_wraprec_create();
wraprec->transience = NR_WRAPREC_IS_TRANSIENT;
wraprec->is_transient = true;

nrl_verbosedebug(NRL_INSTRUMENT, "adding custom for callable '%s'", name);
nr_free(name);
Expand All @@ -397,8 +397,7 @@ nruserfn_t* nr_php_add_custom_tracer_callable(zend_function* func TSRMLS_DC) {
}

nruserfn_t* nr_php_add_custom_tracer_named(const char* namestr,
size_t namestrlen,
nr_transience_t transience TSRMLS_DC) {
size_t namestrlen) {
nruserfn_t* wraprec;
nruserfn_t* p;

Expand Down Expand Up @@ -431,14 +430,11 @@ nruserfn_t* nr_php_add_custom_tracer_named(const char* namestr,
(0 == wraprec->classname) ? "" : "::", NRP_PHP(wraprec->funcname));

nr_php_wrap_user_function_internal(wraprec TSRMLS_CC);
if (transience == NR_WRAPREC_IS_TRANSIENT) {
wraprec->transience = NR_WRAPREC_IS_TRANSIENT;
} else {
/* non-transient wraprecs are added to both the hashmap and linked list.
* At request shutdown, the hashmap will free transients, but leave
* non-transients to be freed when the linked list is disposed of which is at module shutdown */
nr_php_add_custom_tracer_common(wraprec);
}
/* non-transient wraprecs are added to both the hashmap and linked list.
* At request shutdown, the hashmap will free transients, but leave
* non-transients to be freed when the linked list is disposed of which is at
* module shutdown */
nr_php_add_custom_tracer_common(wraprec);

return wraprec; /* return the new wraprec */
}
Expand Down Expand Up @@ -484,7 +480,7 @@ void nr_php_remove_transient_user_instrumentation(void) {
nruserfn_t* prev = NULL;

while (NULL != p) {
if (p->transience == NR_WRAPREC_IS_TRANSIENT) {
if (p->is_transient) {
nruserfn_t* trans = p;

if (prev) {
Expand Down Expand Up @@ -519,19 +515,15 @@ void nr_php_add_user_instrumentation(TSRMLS_D) {

void nr_php_add_transaction_naming_function(const char* namestr,
int namestrlen TSRMLS_DC) {
nruserfn_t* wraprec
= nr_php_add_custom_tracer_named(namestr, namestrlen,
NR_WRAPREC_NOT_TRANSIENT TSRMLS_CC);
nruserfn_t* wraprec = nr_php_add_custom_tracer_named(namestr, namestrlen);

if (NULL != wraprec) {
wraprec->is_names_wt_simple = 1;
}
}

void nr_php_add_custom_tracer(const char* namestr, int namestrlen TSRMLS_DC) {
nruserfn_t* wraprec
= nr_php_add_custom_tracer_named(namestr, namestrlen,
NR_WRAPREC_NOT_TRANSIENT TSRMLS_CC);
nruserfn_t* wraprec = nr_php_add_custom_tracer_named(namestr, namestrlen);

if (NULL != wraprec) {
wraprec->create_metric = 1;
Expand Down Expand Up @@ -588,9 +580,7 @@ void nr_php_user_function_add_declared_callback(const char* namestr,
int namestrlen,
nruserfn_declared_t callback
TSRMLS_DC) {
nruserfn_t* wraprec
= nr_php_add_custom_tracer_named(namestr, namestrlen,
NR_WRAPREC_NOT_TRANSIENT TSRMLS_CC);
nruserfn_t* wraprec = nr_php_add_custom_tracer_named(namestr, namestrlen);

if (0 != wraprec) {
wraprec->declared_callback = callback;
Expand Down
Loading
Loading