Skip to content

Commit

Permalink
feat: instrument wordpress core callbacks on demand (#802)
Browse files Browse the repository at this point in the history
Improve agent's performance by adding a new toggle around WordPress
instrumentation:

* `newrelic.framework.wordpress.core` - indicates if WordPress hooks
callback functions, implemented in WordPress core, are to be
instrumented.

By default WordPress hook callbacks from WordPress core are not
instrumented because it increases agent's overhead. It is however
possible to enable this instrumentation with INI setting.
  • Loading branch information
lavarou committed Jan 17, 2024
1 parent ac78511 commit 5b90fb1
Show file tree
Hide file tree
Showing 25 changed files with 695 additions and 23 deletions.
41 changes: 23 additions & 18 deletions agent/fw_wordpress.c
Original file line number Diff line number Diff line change
Expand Up @@ -337,10 +337,12 @@ NR_PHP_WRAPPER(nr_wordpress_wrap_hook) {
#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 || NRINI(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

Expand Down Expand Up @@ -631,20 +633,23 @@ NR_PHP_WRAPPER(nr_wordpress_add_filter) {
if (true == wrap_hook) {
nruserfn_t* callback_wraprec;
zval* callback = nr_php_arg_get(2, NR_EXECUTE_ORIG_ARGS);
/* the callback here can be any PHP callable. nr_php_wrap_generic_callable
* checks that a valid callable is passed */
callback_wraprec = nr_php_wrap_generic_callable(callback, 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 (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.
zend_function* fn = nr_php_zval_to_function(callback);
callback_wraprec->wordpress_plugin_theme
= nr_wordpress_plugin_from_function(fn);
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 || NRINI(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);
}
Expand Down
1 change: 1 addition & 0 deletions agent/php_newrelic.h
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ nrinibool_t drupal_modules; /* newrelic.framework.drupal.modules */
nrinibool_t wordpress_hooks; /* newrelic.framework.wordpress.hooks */
nrinitime_t wordpress_hooks_threshold; /* newrelic.framework.wordpress.hooks_threshold */
nrinibool_t wordpress_plugins; /* newrelic.framework.wordpress.plugins */
nrinibool_t wordpress_core; /* newrelic.framework.wordpress.core */
nrinistr_t
wordpress_hooks_skip_filename; /* newrelic.framework.wordpress.hooks_skip_filename
*/
Expand Down
10 changes: 9 additions & 1 deletion agent/php_nrini.c
Original file line number Diff line number Diff line change
Expand Up @@ -2215,13 +2215,21 @@ STD_PHP_INI_ENTRY_EX("newrelic.framework.wordpress.hooks_threshold",
newrelic_globals,
0)
STD_PHP_INI_ENTRY_EX("newrelic.framework.wordpress.plugins",
"0",
"1",
NR_PHP_REQUEST,
nr_boolean_mh,
wordpress_plugins,
zend_newrelic_globals,
newrelic_globals,
nr_on_off_dh)
STD_PHP_INI_ENTRY_EX("newrelic.framework.wordpress.core",
"0",
NR_PHP_REQUEST,
nr_boolean_mh,
wordpress_core,
zend_newrelic_globals,
newrelic_globals,
nr_on_off_dh)
STD_PHP_INI_ENTRY_EX("newrelic.framework.wordpress.hooks_skip_filename",
"",
NR_PHP_REQUEST,
Expand Down
12 changes: 10 additions & 2 deletions agent/scripts/newrelic.ini.template
Original file line number Diff line number Diff line change
Expand Up @@ -1178,10 +1178,18 @@ newrelic.daemon.logfile = "/var/log/newrelic/newrelic-daemon.log"
; Setting: newrelic.framework.wordpress.plugins
; Type : boolean
; Scope : per-directory
; Default: false
; Default: true
; Info : Indicates if WordPress plugins are to be instrumented.
;
;newrelic.framework.wordpress.plugins = false
;newrelic.framework.wordpress.plugins = true

; Setting: newrelic.framework.wordpress.core
; Type : boolean
; Scope : per-directory
; Default: false
; Info : Indicates if WordPress core hook callbacks are to be instrumented.
;
;newrelic.framework.wordpress.core = false

; Setting: newrelic.application_logging.enabled
; Type : boolean
Expand Down
38 changes: 38 additions & 0 deletions tests/integration/frameworks/wordpress/mock-wordpress-app.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

/* A simple mock of a WordPress app */

require_once __DIR__.'/wp-config.php';

// Register core action
add_action("wp_init", "wp_core_action_1");
add_action("wp_init", "wp_core_action_2");
add_action("wp_init", "wp_core_action_3");
add_action("wp_init", "wp_core_action_4");

/* Emulate loading custom plugin and theme */
require_once __DIR__.'/wp-content/plugins/mock-plugin1.php';
require_once __DIR__.'/wp-content/plugins/mock-plugin2.php';
require_once __DIR__.'/wp-content/themes/mock-theme1.php';
require_once __DIR__.'/wp-content/themes/mock-theme2.php';

// Register custom plugin action
add_action("wp_loaded", "plugin1_action_1");
add_action("wp_loaded", "plugin2_action_1");

// Emulate WordPress initialization:
do_action("wp_init");
do_action("wp_loaded");

// Register custom theme filter
add_filter("the_content", "wp_core_filter_1");

// Register custom theme filter
add_filter("template_include", "theme1_identity_filter");
add_filter("template_include", "theme2_identity_filter");

// Emulate WordPress loading a template to render a page:
$template = apply_filters("template_include", "./path/to/templates/page-template.php");

// Emulate WordPress rendering the content for the page:
$content = apply_filters("the_content", "Lore ipsum HTML");
54 changes: 54 additions & 0 deletions tests/integration/frameworks/wordpress/test_wordpress_00.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php
/*
* Copyright 2020 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

/*DESCRIPTION
Test WordPress default instrumentation. The agent should:
- detect WordPress framework
- name the web transaction as an 'Action' named after the template used to generate the page
- detect custom plugins and themes
- generate hooks metrics but only when hook executes functions from custom
plugin or theme; the agent must not generate hooks metrics when hook executes
functions from wordpress core
No errors should be generated.
*/

/*SKIPIF
<?php
// This test checks if add_filter is instrumented and this function is only
// instrumented in PHPs 7.4 because it is used to wrap hook callback functions.
// Older PHPs use call_user_func_array instrumentation to wrap hook callbacks.
if (version_compare(PHP_VERSION, '7.4', '<')) {
die("skip: PHP >= 7.4 required\n");
}
*/

/*ENVIRONMENT
REQUEST_METHOD=GET
*/

/*EXPECT_METRICS_EXIST
Supportability/framework/WordPress/detected
WebTransaction/Action/page-template
Supportability/InstrumentedFunction/apply_filters
Supportability/InstrumentedFunction/do_action
Supportability/InstrumentedFunction/add_filter
Framework/WordPress/Hook/wp_loaded
Framework/WordPress/Hook/template_include
Framework/WordPress/Plugin/mock-plugin1
Framework/WordPress/Plugin/mock-plugin2
Framework/WordPress/Plugin/mock-theme1
Framework/WordPress/Plugin/mock-theme2
*/

/*EXPECT_METRICS_DONT_EXIST
Framework/WordPress/Hook/wp_init
Framework/WordPress/Hook/the_content
*/

/*EXPECT_ERROR_EVENTS null */

/* WordPress mock app */
require_once __DIR__.'/mock-wordpress-app.php';
46 changes: 46 additions & 0 deletions tests/integration/frameworks/wordpress/test_wordpress_00.php7.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php
/*
* Copyright 2020 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

/*DESCRIPTION
Test WordPress default instrumentation. The agent should:
- detect WordPress framework
- name the web transaction as an 'Action' named after the template used to generate the page
- detect custom plugins and themes
- generate hooks metrics but only when hook executes functions from custom
plugin or theme; the agent must not generate hooks metrics when hook executes
functions from wordpress core
No errors should be generated.
*/

/*SKIPIF
*/

/*ENVIRONMENT
REQUEST_METHOD=GET
*/

/*EXPECT_METRICS_EXIST
Supportability/framework/WordPress/detected
WebTransaction/Action/page-template
Supportability/InstrumentedFunction/apply_filters
Supportability/InstrumentedFunction/do_action
Framework/WordPress/Hook/wp_loaded
Framework/WordPress/Hook/template_include
Framework/WordPress/Plugin/mock-plugin1
Framework/WordPress/Plugin/mock-plugin2
Framework/WordPress/Plugin/mock-theme1
Framework/WordPress/Plugin/mock-theme2
*/

/*EXPECT_METRICS_DONT_EXIST
Framework/WordPress/Hook/wp_init
Framework/WordPress/Hook/the_content
*/

/*EXPECT_ERROR_EVENTS null */

/* WordPress mock app */
require_once __DIR__.'/mock-wordpress-app.php';
47 changes: 47 additions & 0 deletions tests/integration/frameworks/wordpress/test_wordpress_01.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php
/*
* Copyright 2020 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

/*DESCRIPTION
Test WordPress instrumentation when hooks are not instrumented. The agent should:
- detect WordPress framework
- name the web transaction as an 'Action' named after the template used to generate the page
The agent should not:
- detect custom plugins and themes
- generate hooks metrics
No errors should be generated.
*/

/*INI
newrelic.framework.wordpress.hooks = false
*/

/*ENVIRONMENT
REQUEST_METHOD=GET
*/

/*EXPECT_METRICS_EXIST
Supportability/framework/WordPress/detected
WebTransaction/Action/page-template
Supportability/InstrumentedFunction/apply_filters
*/

/*EXPECT_METRICS_DONT_EXIST
Supportability/InstrumentedFunction/do_action
Supportability/InstrumentedFunction/add_filter
Framework/WordPress/Hook/wp_loaded
Framework/WordPress/Hook/template_include
Framework/WordPress/Plugin/mock-plugin1
Framework/WordPress/Plugin/mock-plugin2
Framework/WordPress/Plugin/mock-theme1
Framework/WordPress/Plugin/mock-theme2
Framework/WordPress/Hook/wp_init
Framework/WordPress/Hook/the_content
*/

/*EXPECT_ERROR_EVENTS null */

/* WordPress mock app */
require_once __DIR__.'/mock-wordpress-app.php';
53 changes: 53 additions & 0 deletions tests/integration/frameworks/wordpress/test_wordpress_02.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php
/*
* Copyright 2020 New Relic Corporation. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*/

/*DESCRIPTION
Test WordPress instrumentation when hooks are instrumented but custom plugins are not.
This setting is not very useful unless wordpress core performance is to be observed.
The agent should:
- detect WordPress framework
- name the web transaction as an 'Action' named after the template used to generate the page
- generate hooks metrics for all callback functions executions that last longer than
threshold duration; the execution time of callback functions from wordpress core,
custom plugins and themes is captured.
The agent should not:
- detect and report custom plugins and themes
No errors should be generated.
*/

/*INI
newrelic.framework.wordpress.hooks = true
newrelic.framework.wordpress.plugins = false
newrelic.framework.wordpress.hooks_threshold = 0
*/

/*ENVIRONMENT
REQUEST_METHOD=GET
*/

/*EXPECT_METRICS_EXIST
Supportability/framework/WordPress/detected
WebTransaction/Action/page-template
Supportability/InstrumentedFunction/apply_filters
Supportability/InstrumentedFunction/do_action
Framework/WordPress/Hook/wp_loaded
Framework/WordPress/Hook/template_include
Framework/WordPress/Hook/wp_init
Framework/WordPress/Hook/the_content
*/

/*EXPECT_METRICS_DONT_EXIST
Supportability/InstrumentedFunction/add_filter
Framework/WordPress/Plugin/mock-plugin1
Framework/WordPress/Plugin/mock-plugin2
Framework/WordPress/Plugin/mock-theme1
Framework/WordPress/Plugin/mock-theme2
*/

/*EXPECT_ERROR_EVENTS null */

/* WordPress mock app */
require_once __DIR__.'/mock-wordpress-app.php';
Loading

0 comments on commit 5b90fb1

Please sign in to comment.