diff --git a/.github/workflows/code-coverage-baseline.yml b/.github/workflows/code-coverage-baseline.yml
index 952ab8459..d5a700c28 100644
--- a/.github/workflows/code-coverage-baseline.yml
+++ b/.github/workflows/code-coverage-baseline.yml
@@ -69,7 +69,7 @@ jobs:
matrix:
platform: [gnu, musl]
arch: [amd64]
- php: ['7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2']
+ php: ['7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3']
include:
- codecov: 0
- platform: gnu
@@ -147,7 +147,7 @@ jobs:
matrix:
platform: [gnu, musl]
arch: [amd64]
- php: ['7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2']
+ php: ['7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3']
include:
- codecov: 0
- platform: gnu
diff --git a/.github/workflows/make-agent.yml b/.github/workflows/make-agent.yml
index b10a2a45d..9d2d76dd5 100644
--- a/.github/workflows/make-agent.yml
+++ b/.github/workflows/make-agent.yml
@@ -32,7 +32,7 @@ jobs:
strategy:
matrix:
platform: [gnu, musl]
- php: ['8.0', '8.1', '8.2']
+ php: ['8.0', '8.1', '8.2', '8.3']
steps:
- name: Checkout Repo
uses: actions/checkout@v3
diff --git a/.github/workflows/make-integration-tests.yml b/.github/workflows/make-integration-tests.yml
index 8128e98cf..ab90a136b 100644
--- a/.github/workflows/make-integration-tests.yml
+++ b/.github/workflows/make-integration-tests.yml
@@ -34,7 +34,7 @@ jobs:
fail-fast: true
matrix:
platform: [gnu, musl]
- php: ['8.0', '8.1', '8.2']
+ php: ['8.0', '8.1', '8.2', '8.3']
steps:
- name: Checkout integration tests
uses: actions/checkout@v3
diff --git a/.github/workflows/repolinter.yml b/.github/workflows/repolinter.yml
index acb921f30..e1e3c1cc1 100644
--- a/.github/workflows/repolinter.yml
+++ b/.github/workflows/repolinter.yml
@@ -8,6 +8,9 @@ name: Repolinter Action
# filtered in the "Test Default Branch" step.
on: [push, workflow_dispatch]
+permissions:
+ issues: write
+
jobs:
repolint:
name: Run Repolinter
diff --git a/.github/workflows/test-agent.yml b/.github/workflows/test-agent.yml
index 1d78f05d8..2736d2cc1 100644
--- a/.github/workflows/test-agent.yml
+++ b/.github/workflows/test-agent.yml
@@ -72,7 +72,7 @@ jobs:
matrix:
platform: [gnu, musl]
arch: [amd64, arm64]
- php: ['7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2']
+ php: ['7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3']
exclude:
- arch: arm64
php: '7.0'
@@ -183,7 +183,7 @@ jobs:
matrix:
platform: [gnu, musl]
arch: [amd64, arm64]
- php: ['7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2']
+ php: ['7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3']
exclude:
- arch: arm64
php: '7.0'
diff --git a/README.md b/README.md
index 6a67babc9..e0bef27e9 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-[![Community Plus header](https://github.com/newrelic/opensource-website/raw/master/src/images/categories/Community_Plus.png)](https://opensource.newrelic.com/oss-category/#community-plus)
+
# New Relic PHP agent [![agent-build status](https://github.com/newrelic/newrelic-php-agent/workflows/agent-build/badge.svg)](https://github.com/newrelic/newrelic-php-agent/actions)
@@ -35,7 +35,7 @@ If the issue is confirmed as a bug or is a feature request, please file a GitHub
**Support Channels**
* [New Relic Documentation](https://docs.newrelic.com/docs/agents/php-agent/getting-started/introduction-new-relic-php): Comprehensive guidance for using our platform
-* [New Relic Community](https://discuss.newrelic.com/tags/phpagent): The best place to engage in troubleshooting questions
+* [New Relic Community](https://forum.newrelic.com/): The best place to engage in troubleshooting questions
* [New Relic Developer](https://developer.newrelic.com/): Resources for building a custom observability applications
* [New Relic University](https://learn.newrelic.com/): A range of online training for New Relic users of every level
* [New Relic Technical Support](https://support.newrelic.com/) 24/7/365 ticketed support. Read more about our [Technical Support Offerings](https://docs.newrelic.com/docs/licenses/license-information/general-usage-licenses/global-technical-support-offerings).
diff --git a/VERSION b/VERSION
index 0ca1348de..f9fb144f9 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-10.14.0
+10.15.0
diff --git a/agent/fw_drupal.c b/agent/fw_drupal.c
index 13fcc68a9..5305ab480 100644
--- a/agent/fw_drupal.c
+++ b/agent/fw_drupal.c
@@ -608,12 +608,16 @@ NR_PHP_WRAPPER(nr_drupal_wrap_module_invoke_all) {
NRPRG(drupal_module_invoke_all_hook)
= nr_strndup(Z_STRVAL_P(hook), Z_STRLEN_P(hook));
NRPRG(drupal_module_invoke_all_hook_len) = Z_STRLEN_P(hook);
+ NRPRG(check_cufa) = true;
NR_PHP_WRAPPER_CALL;
nr_free(NRPRG(drupal_module_invoke_all_hook));
NRPRG(drupal_module_invoke_all_hook) = prev_hook;
NRPRG(drupal_module_invoke_all_hook_len) = prev_hook_len;
+ if (NULL == NRPRG(drupal_module_invoke_all_hook)) {
+ NRPRG(check_cufa) = false;
+ }
leave:
nr_php_arg_release(&hook);
diff --git a/agent/fw_drupal8.c b/agent/fw_drupal8.c
index 05849031a..77695e058 100644
--- a/agent/fw_drupal8.c
+++ b/agent/fw_drupal8.c
@@ -418,7 +418,7 @@ NR_PHP_WRAPPER(nr_drupal94_invoke_all_with) {
NRPRG(drupal_module_invoke_all_hook)
= nr_strndup(Z_STRVAL_P(hook), Z_STRLEN_P(hook));
NRPRG(drupal_module_invoke_all_hook_len) = Z_STRLEN_P(hook);
-
+ NRPRG(check_cufa) = true;
callback = nr_php_arg_get(2, NR_EXECUTE_ORIG_ARGS TSRMLS_CC);
/* This instrumentation will fail if callback has already been wrapped
* with a special instrumentation callback in a different context.
@@ -432,6 +432,9 @@ NR_PHP_WRAPPER(nr_drupal94_invoke_all_with) {
nr_free(NRPRG(drupal_module_invoke_all_hook));
NRPRG(drupal_module_invoke_all_hook) = prev_hook;
NRPRG(drupal_module_invoke_all_hook_len) = prev_hook_len;
+ if (NULL == NRPRG(drupal_module_invoke_all_hook)) {
+ NRPRG(check_cufa) = false;
+ }
leave:
nr_php_arg_release(&hook);
diff --git a/agent/fw_wordpress.c b/agent/fw_wordpress.c
index 4f9544636..3a84450cf 100644
--- a/agent/fw_wordpress.c
+++ b/agent/fw_wordpress.c
@@ -13,6 +13,7 @@
#include "fw_hooks.h"
#include "fw_support.h"
#include "util_logging.h"
+#include "util_matcher.h"
#include "util_memory.h"
#include "util_regex.h"
#include "util_strings.h"
@@ -21,99 +22,24 @@
#define NR_WORDPRESS_HOOK_PREFIX "Framework/WordPress/Hook/"
#define NR_WORDPRESS_PLUGIN_PREFIX "Framework/WordPress/Plugin/"
-static size_t zval_len_without_trailing_slash(const zval* zstr) {
- nr_string_len_t len = Z_STRLEN_P(zstr);
- const char* str = Z_STRVAL_P(zstr);
+static nr_regex_t* wordpress_hook_regex;
- if ((len > 0) && ('/' == str[len - 1])) {
- len -= 1;
- }
-
- return (size_t)len;
-}
-
-static void add_wildcard_path_component(nrbuf_t* buf) {
- nr_buffer_add(buf, NR_PSTR("/(.*?)/|plugins/([^/]*)[.]php$"));
-}
-
-static nr_regex_t* compile_regex_for_path(const zval* path) {
- nrbuf_t* buf = nr_buffer_create(2 * Z_STRLEN_P(path), 0);
- nr_regex_t* regex = NULL;
-
- nr_regex_add_quoted_to_buffer(buf, Z_STRVAL_P(path),
- zval_len_without_trailing_slash(path));
- add_wildcard_path_component(buf);
- nr_buffer_add(buf, NR_PSTR("\0"));
- regex = nr_regex_create(nr_buffer_cptr(buf), NR_REGEX_CASELESS, 1);
-
- nr_buffer_destroy(&buf);
- return regex;
-}
-
-static nr_regex_t* compile_regex_for_path_array(const zval* paths) {
- nrbuf_t* buf = nr_buffer_create(0, 0);
- int first = 1;
- zval* path = NULL;
- nr_regex_t* regex = NULL;
-
- nr_buffer_add(buf, NR_PSTR("("));
- ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(paths), path) {
- if (!nr_php_is_zval_valid_string(path)) {
- nrl_verbosedebug(NRL_FRAMEWORK,
- "%s: unexpected non-string value in path array",
- __func__);
- } else {
- if (first) {
- first = 0;
- } else {
- nr_buffer_add(buf, NR_PSTR("|"));
- }
-
- nr_regex_add_quoted_to_buffer(buf, Z_STRVAL_P(path),
- zval_len_without_trailing_slash(path));
- }
- }
- ZEND_HASH_FOREACH_END();
- nr_buffer_add(buf, NR_PSTR(")"));
- add_wildcard_path_component(buf);
- nr_buffer_add(buf, NR_PSTR("\0"));
-
- /*
- * If the first flag is still set, no valid theme paths existed and we
- * should abort.
- */
- if (first) {
- nrl_verbosedebug(NRL_FRAMEWORK, "%s: no valid elements in the path array",
- __func__);
- } else {
- regex = nr_regex_create(nr_buffer_cptr(buf), NR_REGEX_CASELESS, 1);
- }
-
- nr_buffer_destroy(&buf);
- return regex;
-}
-
-static nr_regex_t* compile_regex_for_constant(const char* constant,
- const char* suffix TSRMLS_DC) {
- nr_regex_t* regex = NULL;
- zval* value = nr_php_get_constant(constant TSRMLS_CC);
+static nr_matcher_t* create_matcher_for_constant(const char* constant,
+ const char* suffix) {
+ zval* value = nr_php_get_constant(constant);
if (nr_php_is_zval_valid_string(value)) {
nrl_verbosedebug(NRL_FRAMEWORK,
"Wordpress: found value = %s for constant=%s",
Z_STRVAL_P(value), constant);
+ nr_matcher_t* matcher = nr_matcher_create();
+ char* prefix = nr_formatf("%s%s", Z_STRVAL_P(value), suffix);
- nrbuf_t* buf = nr_buffer_create(2 * Z_STRLEN_P(value), 0);
+ nr_matcher_add_prefix(matcher, prefix);
- nr_regex_add_quoted_to_buffer(buf, Z_STRVAL_P(value),
- zval_len_without_trailing_slash(value));
- nr_buffer_add(buf, suffix, nr_strlen(suffix));
- add_wildcard_path_component(buf);
- nr_buffer_add(buf, NR_PSTR("\0"));
-
- regex = nr_regex_create(nr_buffer_cptr(buf), NR_REGEX_CASELESS, 1);
-
- nr_buffer_destroy(&buf);
+ nr_free(prefix);
+ nr_php_zval_free(&value);
+ return matcher;
} else {
/*
* If the constant isn't set, that's not a problem, but if it is and it's
@@ -126,51 +52,52 @@ static nr_regex_t* compile_regex_for_constant(const char* constant,
}
nr_php_zval_free(&value);
- return regex;
+ return NULL;
}
-static char* try_match_regex(const nr_regex_t* regex, const char* filename) {
- char* plugin = NULL;
- nr_regex_substrings_t* ss = NULL;
+/*
+ * Purpose : Strip the ".php" file extension from a file name
+ *
+ * Params : 1. The string filename
+ * 2. The filename length
+ *
+ * Returns : A newly allocated string stripped of the .php extension
+ *
+ */
+static inline char* nr_wordpress_strip_php_suffix(char* filename, int filename_len) {
+ char* retval = NULL;
- ss = nr_regex_match_capture(regex, filename, nr_strlen(filename));
- if (NULL == ss) {
- return NULL;
+ if (!nr_striendswith(filename, filename_len, NR_PSTR(".php"))) {
+ return filename;
}
- /*
- * The last substring will be the plugin or theme name.
- */
- plugin = nr_regex_substrings_get(ss, nr_regex_substrings_count(ss));
- nr_regex_substrings_destroy(&ss);
-
- return plugin;
+ retval = nr_strndup(filename, filename_len - (sizeof(".php") - 1));
+ nr_free(filename);
+ return retval;
}
-static const nr_regex_t* nr_wordpress_core_regex(TSRMLS_D) {
- nr_regex_t* regex = NULL;
+static nr_matcher_t* nr_wordpress_core_matcher() {
+ nr_matcher_t* matcher = NULL;
- if (NRPRG(wordpress_core_regex)) {
- return NRPRG(wordpress_core_regex);
+ if (NRPRG(wordpress_core_matcher)) {
+ return NRPRG(wordpress_core_matcher);
}
- /*
- * This will get all the Wordpress core functions located in the `wp-includes`
- * (or a subdirectory off of that directory) directory.
- */
-
- regex
- = nr_regex_create("wp-includes.*?/([^/]*)[.]php$", NR_REGEX_CASELESS, 1);
+ matcher = create_matcher_for_constant("WPINC", "");
+ if (NULL == matcher) {
+ matcher = nr_matcher_create();
+ nr_matcher_add_prefix(matcher, "/wp-includes");
+ }
- NRPRG(wordpress_core_regex) = regex;
- return regex;
+ NRPRG(wordpress_core_matcher) = matcher;
+ return matcher;
}
-static const nr_regex_t* nr_wordpress_plugin_regex(TSRMLS_D) {
- nr_regex_t* regex = NULL;
+static nr_matcher_t* nr_wordpress_plugin_matcher() {
+ nr_matcher_t* matcher = NULL;
- if (NRPRG(wordpress_plugin_regex)) {
- return NRPRG(wordpress_plugin_regex);
+ if (NRPRG(wordpress_plugin_matcher)) {
+ return NRPRG(wordpress_plugin_matcher);
}
/*
@@ -185,35 +112,37 @@ static const nr_regex_t* nr_wordpress_plugin_regex(TSRMLS_D) {
* the best.
*/
- regex = compile_regex_for_constant("WP_PLUGIN_DIR", "" TSRMLS_CC);
- if (!regex) {
- regex = compile_regex_for_constant("WP_CONTENT_DIR", "/plugins" TSRMLS_CC);
+ matcher = create_matcher_for_constant("WP_PLUGIN_DIR", "");
+ if (NULL == matcher) {
+ matcher = create_matcher_for_constant("WP_CONTENT_DIR", "/plugins");
}
/*
* Fallback if the constants didn't exist or were invalid.
*/
- if (NULL == regex) {
+ if (NULL == matcher) {
nrl_verbosedebug(NRL_FRAMEWORK,
"%s: neither WP_PLUGIN_DIR nor WP_CONTENT_DIR set",
__func__);
- regex = nr_regex_create("plugins/(.*?)/|plugins/([^/]*)[.]php$",
- NR_REGEX_CASELESS, 1);
+ matcher = nr_matcher_create();
+ nr_matcher_add_prefix(matcher, "/wp-content/plugins");
}
- NRPRG(wordpress_plugin_regex) = regex;
- return regex;
+ NRPRG(wordpress_plugin_matcher) = matcher;
+ return matcher;
}
-static const nr_regex_t* nr_wordpress_theme_regex(TSRMLS_D) {
- nr_regex_t* regex = NULL;
+static nr_matcher_t* nr_wordpress_theme_matcher() {
+ nr_matcher_t* matcher = NULL;
zval* roots = NULL;
- if (NRPRG(wordpress_theme_regex)) {
- return NRPRG(wordpress_theme_regex);
+ if (NRPRG(wordpress_theme_matcher)) {
+ return NRPRG(wordpress_theme_matcher);
}
+ matcher = nr_matcher_create();
+
/*
* WordPress 2.9.0 and later include get_theme_roots(), which will give us
* either a string with the single theme root (the normal case) or an array
@@ -221,36 +150,51 @@ static const nr_regex_t* nr_wordpress_theme_regex(TSRMLS_D) {
*/
roots = nr_php_call(NULL, "get_theme_roots");
if (nr_php_is_zval_valid_string(roots)) {
- regex = compile_regex_for_path(roots);
+ nr_matcher_add_prefix(matcher, Z_STRVAL_P(roots));
} else if (nr_php_is_zval_valid_array(roots)
&& (nr_php_zend_hash_num_elements(Z_ARRVAL_P(roots)) > 0)) {
- regex = compile_regex_for_path_array(roots);
+ zval* path = NULL;
+ ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(roots), path) {
+ if (nr_php_is_zval_valid_string(path)) {
+ nr_matcher_add_prefix(matcher, Z_STRVAL_P(path));
+ }
+ }
+ ZEND_HASH_FOREACH_END();
+ } else {
+ nr_matcher_add_prefix(matcher, "/wp-content/themes");
}
- nr_php_zval_free(&roots);
- /*
- * Fallback path if get_theme_roots() failed to give us anything useful.
- */
- if (NULL == regex) {
- regex = nr_regex_create("/wp-content/themes/(.*?)/", NR_REGEX_CASELESS, 1);
- }
+ nr_php_zval_free(&roots);
- NRPRG(wordpress_theme_regex) = regex;
- return regex;
+ NRPRG(wordpress_theme_matcher) = matcher;
+ return matcher;
}
-char* nr_php_wordpress_plugin_match_regex(const char* filename TSRMLS_DC) {
+char* nr_php_wordpress_plugin_match_matcher(const char* filename) {
char* plugin = NULL;
- plugin = try_match_regex(nr_wordpress_plugin_regex(TSRMLS_C), filename);
- nr_regex_destroy(&NRPRG(wordpress_plugin_regex));
+ int plugin_len;
+ plugin = nr_matcher_match_ex(nr_wordpress_plugin_matcher(), filename, nr_strlen(filename), &plugin_len);
+ plugin = nr_wordpress_strip_php_suffix(plugin, plugin_len);
+ nr_matcher_destroy(&NRPRG(wordpress_plugin_matcher));
return plugin;
}
-char* nr_php_wordpress_core_match_regex(const char* filename TSRMLS_DC) {
- char* plugin = NULL;
- plugin = try_match_regex(nr_wordpress_core_regex(TSRMLS_C), filename);
- nr_regex_destroy(&NRPRG(wordpress_core_regex));
- return plugin;
+char* nr_php_wordpress_theme_match_matcher(const char* filename) {
+ char* theme = NULL;
+ int plugin_len;
+ theme = nr_matcher_match_ex(nr_wordpress_theme_matcher(), filename, nr_strlen(filename), &plugin_len);
+ theme = nr_wordpress_strip_php_suffix(theme, plugin_len);
+ nr_matcher_destroy(&NRPRG(wordpress_theme_matcher));
+ return theme;
+}
+
+char* nr_php_wordpress_core_match_matcher(const char* filename) {
+ char* core = NULL;
+ int plugin_len;
+ core = nr_matcher_match_r_ex(nr_wordpress_core_matcher(), filename, nr_strlen(filename), &plugin_len);
+ core = nr_wordpress_strip_php_suffix(core, plugin_len);
+ nr_matcher_destroy(&NRPRG(wordpress_core_matcher));
+ return core;
}
static void nr_wordpress_create_metric(nr_segment_t* segment,
@@ -275,6 +219,7 @@ static char* nr_wordpress_plugin_from_function(zend_function* func TSRMLS_DC) {
const char* filename = NULL;
size_t filename_len;
char* plugin = NULL;
+ int plugin_len;
if (NULL == func) {
return NULL;
@@ -288,7 +233,7 @@ static char* nr_wordpress_plugin_from_function(zend_function* func TSRMLS_DC) {
NRP_PHP(NRPRG(wordpress_tag)));
return NULL;
}
- filename_len = nr_strlen(filename);
+ filename_len = nr_php_function_filename_len(func);
if (NRPRG(wordpress_file_metadata)) {
if (nr_hashmap_get_into(NRPRG(wordpress_file_metadata), filename,
@@ -296,7 +241,7 @@ static char* nr_wordpress_plugin_from_function(zend_function* func TSRMLS_DC) {
nrl_verbosedebug(NRL_FRAMEWORK,
"Wordpress: found in cache: "
"plugin= %s and filename=" NRP_FMT,
- plugin, NRP_FILENAME(filename));
+ NRSAFESTR(plugin), NRP_FILENAME(filename));
return plugin;
}
} else {
@@ -306,17 +251,20 @@ static char* nr_wordpress_plugin_from_function(zend_function* func TSRMLS_DC) {
"Wordpress: NOT found in cache: "
"filename=" NRP_FMT,
NRP_FILENAME(filename));
- plugin = try_match_regex(nr_wordpress_plugin_regex(TSRMLS_C), filename);
+ plugin = nr_matcher_match_ex(nr_wordpress_plugin_matcher(), filename, filename_len, &plugin_len);
+ plugin = nr_wordpress_strip_php_suffix(plugin, plugin_len);
if (plugin) {
goto cache_and_return;
}
- plugin = try_match_regex(nr_wordpress_theme_regex(TSRMLS_C), filename);
+ plugin = nr_matcher_match_ex(nr_wordpress_theme_matcher(), filename, filename_len, &plugin_len);
+ plugin = nr_wordpress_strip_php_suffix(plugin, plugin_len);
if (plugin) {
goto cache_and_return;
}
- plugin = try_match_regex(nr_wordpress_core_regex(TSRMLS_C), filename);
+ plugin = nr_matcher_match_r_ex(nr_wordpress_core_matcher(), filename, filename_len, &plugin_len);
+ plugin = nr_wordpress_strip_php_suffix(plugin, plugin_len);
if (plugin) {
/*
* The core wordpress functions are anonymized, so we don't need to return
@@ -340,7 +288,7 @@ static char* nr_wordpress_plugin_from_function(zend_function* func TSRMLS_DC) {
cache_and_return:
/*
* Even if plugin is NULL, we'll still cache that. Hooks in WordPress's core
- * will be NULL, and we need not re-run the regexes each time.
+ * will be NULL, and we need not re-run the matchers each time.
*/
nr_hashmap_set(NRPRG(wordpress_file_metadata), filename, filename_len,
plugin);
@@ -410,6 +358,10 @@ static void nr_wordpress_call_user_func_array(zend_function* func,
nr_php_wrap_callable(func, nr_wordpress_wrap_hook TSRMLS_CC);
}
+static void free_tag(void* tag) {
+ nr_free(tag);
+}
+
/*
* Some plugins generate transient tag names. We can detect these by checking
* the substrings returned from our regex rule. If the tag is transient, we
@@ -418,8 +370,7 @@ static void nr_wordpress_call_user_func_array(zend_function* func,
* Example: (old) add_option__transient_timeout_twccr_382402301f44c883bc0137_cat
* (new) add_option__transient_timeout_twccr_*_cat
*/
-static char* nr_wordpress_clean_tag(const zval* tag TSRMLS_DC) {
- char* orig_tag = NULL;
+static char* nr_wordpress_clean_tag(const zval* tag) {
char* clean_tag = NULL;
nr_regex_t* regex = NULL;
nr_regex_substrings_t* ss = NULL;
@@ -428,13 +379,21 @@ static char* nr_wordpress_clean_tag(const zval* tag TSRMLS_DC) {
return NULL;
}
- regex = NRPRG(wordpress_hook_regex);
+ regex = wordpress_hook_regex;
if (NULL == regex) {
return NULL;
}
- orig_tag = nr_strndup(Z_STRVAL_P(tag), Z_STRLEN_P(tag));
- ss = nr_regex_match_capture(regex, orig_tag, nr_strlen(orig_tag));
+ if (NULL == NRPRG(wordpress_clean_tag_cache)) {
+ NRPRG(wordpress_clean_tag_cache) = nr_hashmap_create(free_tag);
+ }
+
+ if (nr_hashmap_get_into(NRPRG(wordpress_clean_tag_cache), Z_STRVAL_P(tag),
+ Z_STRLEN_P(tag), (void**)&clean_tag)) {
+ return clean_tag;
+ }
+
+ ss = nr_regex_match_capture(regex, Z_STRVAL_P(tag), Z_STRLEN_P(tag));
clean_tag = nr_regex_substrings_get(ss, 5);
/*
@@ -455,7 +414,9 @@ static char* nr_wordpress_clean_tag(const zval* tag TSRMLS_DC) {
}
nr_regex_substrings_destroy(&ss);
- nr_free(orig_tag);
+
+ nr_hashmap_set(NRPRG(wordpress_clean_tag_cache), Z_STRVAL_P(tag),
+ Z_STRLEN_P(tag), clean_tag);
return clean_tag;
}
@@ -466,28 +427,34 @@ NR_PHP_WRAPPER(nr_wordpress_exec_handle_tag) {
NR_UNUSED_SPECIALFN;
(void)wraprec;
- NR_PHP_WRAPPER_REQUIRE_FRAMEWORK(NR_FW_WORDPRESS);
+ if (nrlikely(0 != NRINI(wordpress_hooks))) {
+ NR_PHP_WRAPPER_REQUIRE_FRAMEWORK(NR_FW_WORDPRESS);
- tag = nr_php_arg_get(1, NR_EXECUTE_ORIG_ARGS TSRMLS_CC);
+ tag = nr_php_arg_get(1, NR_EXECUTE_ORIG_ARGS TSRMLS_CC);
- if (1 == nr_php_is_zval_non_empty_string(tag)
- || (0 != NRINI(wordpress_hooks))) {
- /*
- * Our general approach here is to set the wordpress_tag global, then let
- * the call_user_func_array instrumentation take care of actually timing
- * the hooks by checking if it's set.
- */
- char* old_tag = NRPRG(wordpress_tag);
+ if (1 == nr_php_is_zval_non_empty_string(tag)) {
+ /*
+ * Our general approach here is to set the wordpress_tag global, then let
+ * the call_user_func_array instrumentation take care of actually timing
+ * the hooks by checking if it's set.
+ */
+ char* old_tag = NRPRG(wordpress_tag);
- NRPRG(wordpress_tag) = nr_wordpress_clean_tag(tag TSRMLS_CC);
- NR_PHP_WRAPPER_CALL;
- nr_free(NRPRG(wordpress_tag));
- NRPRG(wordpress_tag) = old_tag;
- } else {
- NR_PHP_WRAPPER_CALL;
- }
+ NRPRG(check_cufa) = true;
- nr_php_arg_release(&tag);
+ NRPRG(wordpress_tag) = nr_wordpress_clean_tag(tag);
+ NR_PHP_WRAPPER_CALL;
+ NRPRG(wordpress_tag) = old_tag;
+ if (NULL == NRPRG(wordpress_tag)) {
+ NRPRG(check_cufa) = false;
+ }
+ } else {
+ NRPRG(check_cufa) = false;
+ NR_PHP_WRAPPER_CALL;
+ }
+
+ nr_php_arg_release(&tag);
+ }
}
NR_PHP_WRAPPER_END
@@ -561,11 +528,17 @@ NR_PHP_WRAPPER(nr_wordpress_apply_filters) {
*/
char* old_tag = NRPRG(wordpress_tag);
- NRPRG(wordpress_tag) = nr_wordpress_clean_tag(tag TSRMLS_CC);
+ NRPRG(check_cufa) = true;
+
+ NRPRG(wordpress_tag) = nr_wordpress_clean_tag(tag);
+
NR_PHP_WRAPPER_CALL;
- nr_free(NRPRG(wordpress_tag));
NRPRG(wordpress_tag) = old_tag;
+ if (NULL == NRPRG(wordpress_tag)) {
+ NRPRG(check_cufa) = false;
+ }
} else {
+ NRPRG(check_cufa) = false;
NR_PHP_WRAPPER_CALL;
}
@@ -582,15 +555,27 @@ void nr_wordpress_enable(TSRMLS_D) {
nr_php_wrap_user_function(NR_PSTR("apply_filters"),
nr_wordpress_apply_filters TSRMLS_CC);
- nr_php_wrap_user_function(NR_PSTR("apply_filters_ref_array"),
- nr_wordpress_exec_handle_tag TSRMLS_CC);
+ if (0 != NRINI(wordpress_hooks)) {
+ nr_php_wrap_user_function(NR_PSTR("apply_filters_ref_array"),
+ nr_wordpress_exec_handle_tag TSRMLS_CC);
+
+ nr_php_wrap_user_function(NR_PSTR("do_action"),
+ nr_wordpress_exec_handle_tag TSRMLS_CC);
+
+ nr_php_wrap_user_function(NR_PSTR("do_action_ref_array"),
+ nr_wordpress_exec_handle_tag TSRMLS_CC);
- nr_php_wrap_user_function(NR_PSTR("do_action"),
- nr_wordpress_exec_handle_tag TSRMLS_CC);
+ nr_php_add_call_user_func_array_pre_callback(
+ nr_wordpress_call_user_func_array TSRMLS_CC);
+ }
+}
- nr_php_wrap_user_function(NR_PSTR("do_action_ref_array"),
- nr_wordpress_exec_handle_tag TSRMLS_CC);
+void nr_wordpress_minit(void) {
+ wordpress_hook_regex = nr_regex_create(
+ "(^([a-z_-]+[_-])([0-9a-f_.]+[0-9][0-9a-f.]+)(_{0,1}.*)$|(.*))",
+ NR_REGEX_CASELESS, 0);
+}
- nr_php_add_call_user_func_array_pre_callback(
- nr_wordpress_call_user_func_array TSRMLS_CC);
+void nr_wordpress_mshutdown(void) {
+ nr_regex_destroy(&wordpress_hook_regex);
}
diff --git a/agent/fw_wordpress.h b/agent/fw_wordpress.h
index 3ec916ab1..d6b121cc3 100644
--- a/agent/fw_wordpress.h
+++ b/agent/fw_wordpress.h
@@ -11,22 +11,34 @@
#define FW_WORDPRESS_HDR
/*
- * Purpose : ONLY for testing to verify that the appropriate regex was created
+ * Purpose : ONLY for testing to verify that the appropriate matcher was created
* for determining if a filename belongs to the WP core (located
- * off of the `wp-includes` directory). It destroys the regex at
+ * off of the `wp-includes` directory). It destroys the matcher at
* the end so again, this is only for testing purposes.
*
* Returns : The matching core name; otherwise NULL.
*/
-char* nr_php_wordpress_core_match_regex(const char* filename TSRMLS_DC);
+char* nr_php_wordpress_core_match_matcher(const char* filename);
/*
- * Purpose : ONLY for testing to verify that the appropriate regex was created
+ * Purpose : ONLY for testing to verify that the appropriate matcher was created
* for determining if a filename belongs to a plugin. It destroys
- * the regex at the end so again, this is only for testing purposes.
+ * the matcher at the end so again, this is only for testing purposes.
*
* Returns : The matching plugin; otherwise NULL
*/
-char* nr_php_wordpress_plugin_match_regex(const char* filename TSRMLS_DC);
+char* nr_php_wordpress_plugin_match_matcher(const char* filename);
+
+/*
+ * Purpose : ONLY for testing to verify that the appropriate matcher was created
+ * for determining if a filename belongs to a theme. It destroys
+ * the matcher at the end so again, this is only for testing purposes.
+ *
+ * Returns : The matching theme; otherwise NULL
+ */
+char* nr_php_wordpress_theme_match_matcher(const char* filename);
+
+extern void nr_wordpress_minit(void);
+extern void nr_wordpress_mshutdown(void);
#endif /* FW_WORDPRESS_HDR */
diff --git a/agent/newrelic-install.sh b/agent/newrelic-install.sh
index 27edb843b..d538bb260 100755
--- a/agent/newrelic-install.sh
+++ b/agent/newrelic-install.sh
@@ -335,10 +335,10 @@ for pmv in "20151012" "20160303" "20170718" "20180731" "20190902"; do
done
fi
# Currently supported versions:
-# (8.0, 8.1, 8.2)
+# (8.0, 8.1, 8.2, 8.3)
# for x64 and aarch64
if [ ${arch} = x64 ] || [ ${arch} = aarch64 ]; then
- for pmv in "20200930" "20210902" "20220829"; do
+ for pmv in "20200930" "20210902" "20220829" "20230831"; do
check_file "${ilibdir}/agent/${arch}/newrelic-${pmv}.so"
done
fi
@@ -543,6 +543,7 @@ add_to_path /usr/local/php-7.4/bin
add_to_path /usr/local/php-8.0/bin
add_to_path /usr/local/php-8.1/bin
add_to_path /usr/local/php-8.2/bin
+add_to_path /usr/local/php-8.3/bin
add_to_path /opt/local/bin
add_to_path /usr/php/bin
@@ -555,6 +556,7 @@ add_to_path /usr/php-7.4/bin
add_to_path /usr/php-8.0/bin
add_to_path /usr/php-8.1/bin
add_to_path /usr/php-8.2/bin
+add_to_path /usr/php-8.3/bin
add_to_path /usr/php/7.0/bin
add_to_path /usr/php/7.1/bin
@@ -564,6 +566,7 @@ add_to_path /usr/php/7.4/bin
add_to_path /usr/php/8.0/bin
add_to_path /usr/php/8.1/bin
add_to_path /usr/php/8.2/bin
+add_to_path /usr/php/8.3/bin
add_to_path /opt/php/bin
add_to_path /opt/zend/bin
@@ -576,6 +579,7 @@ add_to_path /opt/php-7.4/bin
add_to_path /opt/php-8.0/bin
add_to_path /opt/php-8.1/bin
add_to_path /opt/php-8.2/bin
+add_to_path /opt/php-8.3/bin
if [ -n "${NR_INSTALL_PATH}" ]; then
oIFS="${IFS}"
@@ -1060,6 +1064,10 @@ for this copy of PHP. We apologize for the inconvenience.
pi_php8="yes"
;;
+ 8.3.*)
+ pi_php8="yes"
+ ;;
+
*)
error "unsupported version '${pi_ver}' of PHP found at:
${pdir}
@@ -1239,6 +1247,7 @@ does not exist. This particular instance of PHP will be skipped.
8.0.*) pi_modver="20200930" ;;
8.1.*) pi_modver="20210902" ;;
8.2.*) pi_modver="20220829" ;;
+ 8.3.*) pi_modver="20230831" ;;
esac
log "${pdir}: pi_modver=${pi_modver}"
diff --git a/agent/php_agent.h b/agent/php_agent.h
index 005daebbc..1c024dc54 100644
--- a/agent/php_agent.h
+++ b/agent/php_agent.h
@@ -671,6 +671,11 @@ nr_php_op_array_file_name(const zend_op_array* op_array) {
#endif
}
+static inline nr_string_len_t NRPURE
+nr_php_op_array_file_name_len(const zend_op_array* op_array) {
+ return op_array->filename ? op_array->filename->len : 0;
+}
+
static inline const char* NRPURE
nr_php_op_array_function_name(const zend_op_array* op_array) {
#if ZEND_MODULE_API_NO >= ZEND_7_0_X_API_NO /* PHP 7.0+ */
@@ -697,6 +702,11 @@ nr_php_op_array_scope_name(const zend_op_array* op_array) {
return NULL;
}
+static inline nr_string_len_t NRPURE
+nr_php_function_filename_len(zend_function* func) {
+ return func ? nr_php_op_array_file_name_len(&func->op_array) : 0;
+}
+
static inline const char* NRPURE
nr_php_ini_entry_name(const zend_ini_entry* entry) {
#if ZEND_MODULE_API_NO >= ZEND_7_0_X_API_NO /* PHP 7.0+ */
diff --git a/agent/php_environment.c b/agent/php_environment.c
index 60f2a2c74..24f390b1d 100644
--- a/agent/php_environment.c
+++ b/agent/php_environment.c
@@ -608,9 +608,6 @@ void nr_php_gather_v2_docker_id() {
if (NULL != dockerId) {
NR_PHP_PROCESS_GLOBALS(docker_id) = dockerId;
nrl_verbosedebug(NRL_AGENT, "%s: Docker v2 ID: %s", __func__, dockerId);
- } else {
- nrl_warning(NRL_AGENT, "%s: Unable to read docker v2 container id",
- __func__);
}
}
diff --git a/agent/php_execute.c b/agent/php_execute.c
index a6bb53ed5..9cd08ee6e 100644
--- a/agent/php_execute.c
+++ b/agent/php_execute.c
@@ -310,6 +310,7 @@ typedef struct _nr_framework_table_t {
const char* framework_name;
const char* config_name;
const char* file_to_check;
+ size_t file_to_check_len;
nr_framework_special_fn_t special;
nr_framework_enable_fn_t enable;
nrframework_t detected;
@@ -322,15 +323,16 @@ typedef struct _nr_framework_table_t {
*
* Note that all paths should be in lowercase.
*/
+// clang-format: off
static const nr_framework_table_t all_frameworks[] = {
/*
* Watch out:
* cake1.2 and cake1.3 use a subdirectory named 'cake' (lower case)
* cake2.0 and on use a subdirectory named 'Cake' (upper case file name)
*/
- {"CakePHP", "cakephp", "cake/libs/object.php", nr_cakephp_special_1,
+ {"CakePHP", "cakephp", NR_PSTR("cake/libs/object.php"), nr_cakephp_special_1,
nr_cakephp_enable_1, NR_FW_CAKEPHP},
- {"CakePHP", "cakephp", "cake/core/app.php", nr_cakephp_special_2,
+ {"CakePHP", "cakephp", NR_PSTR("cake/core/app.php"), nr_cakephp_special_2,
nr_cakephp_enable_2, NR_FW_CAKEPHP},
/*
@@ -340,87 +342,88 @@ static const nr_framework_table_t all_frameworks[] = {
* specifically a problem for Expression Engine (look for expression_engine,
* below.)
*/
- {"CodeIgniter", "codeigniter", "codeigniter.php", 0, nr_codeigniter_enable,
+ {"CodeIgniter", "codeigniter", NR_PSTR("codeigniter.php"), 0, nr_codeigniter_enable,
NR_FW_CODEIGNITER},
- {"Drupal8", "drupal8", "core/includes/bootstrap.inc", 0, nr_drupal8_enable,
+ {"Drupal8", "drupal8", NR_PSTR("core/includes/bootstrap.inc"), 0, nr_drupal8_enable,
NR_FW_DRUPAL8},
- {"Drupal", "drupal", "includes/common.inc", 0, nr_drupal_enable,
+ {"Drupal", "drupal", NR_PSTR("includes/common.inc"), 0, nr_drupal_enable,
NR_FW_DRUPAL},
- {"Joomla", "joomla", "joomla/import.php", 0, nr_joomla_enable,
+ {"Joomla", "joomla", NR_PSTR("joomla/import.php"), 0, nr_joomla_enable,
NR_FW_JOOMLA}, /* <= Joomla 1.5 */
- {"Joomla", "joomla", "libraries/joomla/factory.php", 0, nr_joomla_enable,
+ {"Joomla", "joomla", NR_PSTR("libraries/joomla/factory.php"), 0, nr_joomla_enable,
NR_FW_JOOMLA}, /* >= Joomla 1.6, including 2.5 and 3.2 */
- {"Kohana", "kohana", "kohana/core.php", 0, nr_kohana_enable, NR_FW_KOHANA},
- {"Kohana", "kohana", "kohana/core.php", 0, nr_kohana_enable, NR_FW_KOHANA},
+ {"Kohana", "kohana", NR_PSTR("kohana/core.php"), 0, nr_kohana_enable, NR_FW_KOHANA},
+ {"Kohana", "kohana", NR_PSTR("kohana/core.php"), 0, nr_kohana_enable, NR_FW_KOHANA},
/* See below: Zend, the legacy project of Laminas, which shares much
of the instrumentation implementation with Laminas */
- {"Laminas3", "laminas3", "laminas/mvc/application.php", 0,
+ {"Laminas3", "laminas3", NR_PSTR("laminas/mvc/application.php"), 0,
nr_laminas3_enable, NR_FW_LAMINAS3},
- {"Laminas3", "laminas3", "laminas-mvc/src/application.php", 0,
+ {"Laminas3", "laminas3", NR_PSTR("laminas-mvc/src/application.php"), 0,
nr_laminas3_enable, NR_FW_LAMINAS3},
- {"Laravel", "laravel", "illuminate/foundation/application.php", 0,
+ {"Laravel", "laravel", NR_PSTR("illuminate/foundation/application.php"), 0,
nr_laravel_enable, NR_FW_LARAVEL},
- {"Laravel", "laravel", "bootstrap/compiled.php", 0, nr_laravel_enable,
+ {"Laravel", "laravel", NR_PSTR("bootstrap/compiled.php"), 0, nr_laravel_enable,
NR_FW_LARAVEL}, /* 4.x */
- {"Laravel", "laravel", "storage/framework/compiled.php", 0,
+ {"Laravel", "laravel", NR_PSTR("storage/framework/compiled.php"), 0,
nr_laravel_enable, NR_FW_LARAVEL}, /* 5.0.0-14 */
- {"Laravel", "laravel", "vendor/compiled.php", 0, nr_laravel_enable,
+ {"Laravel", "laravel", NR_PSTR("vendor/compiled.php"), 0, nr_laravel_enable,
NR_FW_LARAVEL}, /* 5.0.15-5.0.x */
- {"Laravel", "laravel", "bootstrap/cache/compiled.php", 0, nr_laravel_enable,
+ {"Laravel", "laravel", NR_PSTR("bootstrap/cache/compiled.php"), 0, nr_laravel_enable,
NR_FW_LARAVEL}, /* 5.1.0-x */
- {"Laravel", "laravel", "bootstrap/app.php", 0, nr_laravel_enable,
+ {"Laravel", "laravel", NR_PSTR("bootstrap/app.php"), 0, nr_laravel_enable,
NR_FW_LARAVEL}, /* 8+ */
- {"Lumen", "lumen", "lumen-framework/src/helpers.php", 0, nr_lumen_enable,
+ {"Lumen", "lumen", NR_PSTR("lumen-framework/src/helpers.php"), 0, nr_lumen_enable,
NR_FW_LUMEN},
- {"Magento", "magento", "app/mage.php", 0, nr_magento1_enable,
+ {"Magento", "magento", NR_PSTR("app/mage.php"), 0, nr_magento1_enable,
NR_FW_MAGENTO1},
- {"Magento2", "magento2", "magento/framework/app/bootstrap.php", 0,
+ {"Magento2", "magento2", NR_PSTR("magento/framework/app/bootstrap.php"), 0,
nr_magento2_enable, NR_FW_MAGENTO2},
- {"MediaWiki", "mediawiki", "includes/webstart.php", 0, nr_mediawiki_enable,
+ {"MediaWiki", "mediawiki", NR_PSTR("includes/webstart.php"), 0, nr_mediawiki_enable,
NR_FW_MEDIAWIKI},
- {"Silex", "silex", "silex/application.php", 0, nr_silex_enable,
+ {"Silex", "silex", NR_PSTR("silex/application.php"), 0, nr_silex_enable,
NR_FW_SILEX},
- {"Slim", "slim", "slim/slim/app.php", 0, nr_slim_enable,
+ {"Slim", "slim", NR_PSTR("slim/slim/app.php"), 0, nr_slim_enable,
NR_FW_SLIM}, /* 3.x */
- {"Slim", "slim", "slim/slim/slim.php", 0, nr_slim_enable,
+ {"Slim", "slim", NR_PSTR("slim/slim/slim.php"), 0, nr_slim_enable,
NR_FW_SLIM}, /* 2.x */
- {"Symfony", "symfony1", "sfcontext.class.php", 0, nr_symfony1_enable,
+ {"Symfony", "symfony1", NR_PSTR("sfcontext.class.php"), 0, nr_symfony1_enable,
NR_FW_SYMFONY1},
- {"Symfony", "symfony1", "sfconfig.class.php", 0, nr_symfony1_enable,
+ {"Symfony", "symfony1", NR_PSTR("sfconfig.class.php"), 0, nr_symfony1_enable,
NR_FW_SYMFONY1},
- {"Symfony2", "symfony2", "bootstrap.php.cache", 0, nr_symfony2_enable,
+ {"Symfony2", "symfony2", NR_PSTR("bootstrap.php.cache"), 0, nr_symfony2_enable,
NR_FW_SYMFONY2}, /* also Symfony 3 */
{"Symfony2", "symfony2",
- "symfony/bundle/frameworkbundle/frameworkbundle.php", 0,
+ NR_PSTR("symfony/bundle/frameworkbundle/frameworkbundle.php"), 0,
nr_symfony2_enable, NR_FW_SYMFONY2}, /* also Symfony 3 */
- {"Symfony4", "symfony4", "http-kernel/httpkernel.php", 0,
+ {"Symfony4", "symfony4", NR_PSTR("http-kernel/httpkernel.php"), 0,
nr_symfony4_enable, NR_FW_SYMFONY4}, /* also Symfony 5 */
- {"WordPress", "wordpress", "wp-config.php", 0, nr_wordpress_enable,
+ {"WordPress", "wordpress", NR_PSTR("wp-config.php"), 0, nr_wordpress_enable,
NR_FW_WORDPRESS},
- {"Yii", "yii", "framework/yii.php", 0, nr_yii_enable, NR_FW_YII},
- {"Yii", "yii", "framework/yiilite.php", 0, nr_yii_enable, NR_FW_YII},
+ {"Yii", "yii", NR_PSTR("framework/yii.php"), 0, nr_yii_enable, NR_FW_YII},
+ {"Yii", "yii", NR_PSTR("framework/yiilite.php"), 0, nr_yii_enable, NR_FW_YII},
/* See above: Laminas, the successor to Zend, which shares much
of the instrumentation implementation with Zend */
- {"Zend", "zend", "zend/loader.php", 0, nr_zend_enable, NR_FW_ZEND},
- {"Zend2", "zend2", "zend/mvc/application.php", 0, nr_fw_zend2_enable,
+ {"Zend", "zend", NR_PSTR("zend/loader.php"), 0, nr_zend_enable, NR_FW_ZEND},
+ {"Zend2", "zend2", NR_PSTR("zend/mvc/application.php"), 0, nr_fw_zend2_enable,
NR_FW_ZEND2},
- {"Zend2", "zend2", "zend-mvc/src/application.php", 0, nr_fw_zend2_enable,
+ {"Zend2", "zend2", NR_PSTR("zend-mvc/src/application.php"), 0, nr_fw_zend2_enable,
NR_FW_ZEND2},
};
+// clang-format: on
static const int num_all_frameworks
= sizeof(all_frameworks) / sizeof(nr_framework_table_t);
@@ -467,30 +470,32 @@ nrframework_t nr_php_framework_from_config(const char* config_name) {
typedef struct _nr_library_table_t {
const char* library_name;
const char* file_to_check;
+ size_t file_to_check_len;
nr_library_enable_fn_t enable;
} nr_library_table_t;
/*
* Note that all paths should be in lowercase.
*/
+// clang-format: off
static nr_library_table_t libraries[] = {
- {"Doctrine 2", "doctrine/orm/query.php", nr_doctrine2_enable},
- {"Guzzle 3", "guzzle/http/client.php", nr_guzzle3_enable},
- {"Guzzle 4-5", "hasemitterinterface.php", nr_guzzle4_enable},
- {"Guzzle 6", "guzzle/src/functions_include.php", nr_guzzle6_enable},
+ {"Doctrine 2", NR_PSTR("doctrine/orm/query.php"), nr_doctrine2_enable},
+ {"Guzzle 3", NR_PSTR("guzzle/http/client.php"), nr_guzzle3_enable},
+ {"Guzzle 4-5", NR_PSTR("hasemitterinterface.php"), nr_guzzle4_enable},
+ {"Guzzle 6", NR_PSTR("guzzle/src/functions_include.php"), nr_guzzle6_enable},
- {"MongoDB", "mongodb/src/client.php", nr_mongodb_enable},
+ {"MongoDB", NR_PSTR("mongodb/src/client.php"), nr_mongodb_enable},
/*
* The first path is for Composer installs, the second is for
* /usr/local/bin. While BaseTestRunner isn't the very first file to load,
* it contains the test status constants and loads before tests can run.
*/
- {"PHPUnit", "phpunit/src/runner/basetestrunner.php", nr_phpunit_enable},
- {"PHPUnit", "phpunit/runner/basetestrunner.php", nr_phpunit_enable},
+ {"PHPUnit", NR_PSTR("phpunit/src/runner/basetestrunner.php"), nr_phpunit_enable},
+ {"PHPUnit", NR_PSTR("phpunit/runner/basetestrunner.php"), nr_phpunit_enable},
- {"Predis", "predis/src/client.php", nr_predis_enable},
- {"Predis", "predis/client.php", nr_predis_enable},
+ {"Predis", NR_PSTR("predis/src/client.php"), nr_predis_enable},
+ {"Predis", NR_PSTR("predis/client.php"), nr_predis_enable},
/*
* Allow Zend Framework 1.x to be detected as a library as well as a
@@ -498,14 +503,14 @@ static nr_library_table_t libraries[] = {
* with other frameworks or even without a framework at all. This is
* necessary for Magento in particular, which is built on ZF1.
*/
- {"Zend_Http", "zend/http/client.php", nr_zend_http_enable},
+ {"Zend_Http", NR_PSTR("zend/http/client.php"), nr_zend_http_enable},
/*
* Allow Laminas Framework 3.x to be detected as a library as well as a
* framework. This allows Laminas_Http_Client to be instrumented when used
* with other frameworks or even without a framework at all.
*/
- {"Laminas_Http", "laminas-http/src/client.php", nr_laminas_http_enable},
+ {"Laminas_Http", NR_PSTR("laminas-http/src/client.php"), nr_laminas_http_enable},
/*
* Other frameworks, detected only, but not specifically
@@ -513,70 +518,73 @@ static nr_library_table_t libraries[] = {
* detection of a supported framework or library later (since a transaction
* can only have one framework).
*/
- {"Aura1", "aura/framework/system.php", NULL},
- {"Aura2", "aura/di/src/containerinterface.php", NULL},
- {"Aura3", "aura/di/src/containerconfiginterface.php", NULL},
- {"CakePHP3", "cakephp/src/core/functions.php", NULL},
- {"Fuel", "fuel/core/classes/fuel.php", NULL},
- {"Lithium", "lithium/core/libraries.php", NULL},
- {"Phpbb", "phpbb/request/request.php", NULL},
- {"Phpixie2", "phpixie/core/classes/phpixie/pixie.php", NULL},
- {"Phpixie3", "phpixie/framework.php", NULL},
- {"React", "react/event-loop/src/loopinterface.php", NULL},
- {"SilverStripe", "injector/silverstripeinjectioncreator.php", NULL},
- {"SilverStripe4", "silverstripeserviceconfigurationlocator.php", NULL},
- {"Typo3", "classes/typo3/flow/core/bootstrap.php", NULL},
- {"Typo3", "typo3/sysext/core/classes/core/bootstrap.php", NULL},
- {"Yii2", "yii2/baseyii.php", NULL},
+ {"Aura1", NR_PSTR("aura/framework/system.php"), NULL},
+ {"Aura2", NR_PSTR("aura/di/src/containerinterface.php"), NULL},
+ {"Aura3", NR_PSTR("aura/di/src/containerconfiginterface.php"), NULL},
+ {"CakePHP3", NR_PSTR("cakephp/src/core/functions.php"), NULL},
+ {"Fuel", NR_PSTR("fuel/core/classes/fuel.php"), NULL},
+ {"Lithium", NR_PSTR("lithium/core/libraries.php"), NULL},
+ {"Phpbb", NR_PSTR("phpbb/request/request.php"), NULL},
+ {"Phpixie2", NR_PSTR("phpixie/core/classes/phpixie/pixie.php"), NULL},
+ {"Phpixie3", NR_PSTR("phpixie/framework.php"), NULL},
+ {"React", NR_PSTR("react/event-loop/src/loopinterface.php"), NULL},
+ {"SilverStripe", NR_PSTR("injector/silverstripeinjectioncreator.php"), NULL},
+ {"SilverStripe4", NR_PSTR("silverstripeserviceconfigurationlocator.php"), NULL},
+ {"Typo3", NR_PSTR("classes/typo3/flow/core/bootstrap.php"), NULL},
+ {"Typo3", NR_PSTR("typo3/sysext/core/classes/core/bootstrap.php"), NULL},
+ {"Yii2", NR_PSTR("yii2/baseyii.php"), NULL},
/*
* Other CMS (content management systems), detected only, but
* not specifically instrumented.
*/
- {"Moodle", "moodlelib.php", NULL},
+ {"Moodle", NR_PSTR("moodlelib.php"), NULL},
/*
* It is likely that this will never be found, since the CodeIgniter.php
* will get loaded first, and as such mark this transaction as belonging to
* CodeIgniter, and not Expession Engine.
*/
- {"ExpressionEngine", "system/expressionengine/config/config.php", NULL},
+ {"ExpressionEngine", NR_PSTR("system/expressionengine/config/config.php"), NULL},
/*
* ExpressionEngine 5, however, has a very obvious file we can look for.
*/
- {"ExpressionEngine5", "expressionengine/boot/boot.php", NULL},
+ {"ExpressionEngine5", NR_PSTR("expressionengine/boot/boot.php"), NULL},
/*
* DokuWiki uses doku.php as an entry point, but has other files that are
* loaded directly that this won't pick up. That's probably OK for
* supportability metrics, but we'll add the most common name for the
* configuration file as well just in case.
*/
- {"DokuWiki", "doku.php", NULL},
- {"DokuWiki", "conf/dokuwiki.php", NULL},
+ {"DokuWiki", NR_PSTR("doku.php"), NULL},
+ {"DokuWiki", NR_PSTR("conf/dokuwiki.php"), NULL},
/*
* SugarCRM no longer has a community edition, so this likely only works
* with older versions.
*/
- {"SugarCRM", "sugarobjects/sugarconfig.php", NULL},
+ {"SugarCRM", NR_PSTR("sugarobjects/sugarconfig.php"), NULL},
- {"Xoops", "class/xoopsload.php", NULL},
- {"E107", "e107_handlers/e107_class.php", NULL},
+ {"Xoops", NR_PSTR("class/xoopsload.php"), NULL},
+ {"E107", NR_PSTR("e107_handlers/e107_class.php"), NULL},
};
+// clang-format: on
static size_t num_libraries = sizeof(libraries) / sizeof(nr_library_table_t);
+// clang-format: off
static nr_library_table_t logging_frameworks[] = {
/* Monolog - Logging for PHP */
- {"Monolog", "monolog/logger.php", nr_monolog_enable},
+ {"Monolog", NR_PSTR("monolog/logger.php"), nr_monolog_enable},
/* Consolidation/Log - Logging for PHP */
- {"Consolidation/Log", "consolidation/log/src/logger.php", NULL},
+ {"Consolidation/Log", NR_PSTR("consolidation/log/src/logger.php"), NULL},
/* laminas-log - Logging for PHP */
- {"laminas-log", "laminas-log/src/logger.php", NULL},
+ {"laminas-log", NR_PSTR("laminas-log/src/logger.php"), NULL},
/* cakephp-log - Logging for PHP */
- {"cakephp-log", "cakephp/log/log.php", NULL},
+ {"cakephp-log", NR_PSTR("cakephp/log/log.php"), NULL},
/* Analog - Logging for PHP */
- {"Analog", "analog/analog.php", NULL},
+ {"Analog", NR_PSTR("analog/analog.php"), NULL},
};
+// clang-format: on
static size_t num_logging_frameworks
= sizeof(logging_frameworks) / sizeof(nr_library_table_t);
@@ -705,7 +713,8 @@ static void nr_php_show_exec_return(NR_EXECUTE_PROTO TSRMLS_DC) {
static nrframework_t nr_try_detect_framework(
const nr_framework_table_t frameworks[],
size_t num_frameworks,
- const char* filename TSRMLS_DC);
+ const char* filename,
+ const size_t filename_len TSRMLS_DC);
static nrframework_t nr_try_force_framework(
const nr_framework_table_t frameworks[],
size_t num_frameworks,
@@ -765,7 +774,8 @@ void nr_framework_create_metric(TSRMLS_D) {
*/
static void nr_execute_handle_framework(const nr_framework_table_t frameworks[],
size_t num_frameworks,
- const char* filename TSRMLS_DC) {
+ const char* filename,
+ const size_t filename_len TSRMLS_DC) {
if (NR_FW_UNSET != NRPRG(current_framework)) {
return;
}
@@ -773,8 +783,8 @@ static void nr_execute_handle_framework(const nr_framework_table_t frameworks[],
if (NR_FW_UNSET == NRINI(force_framework)) {
nrframework_t detected_framework = NR_FW_UNSET;
- detected_framework = nr_try_detect_framework(frameworks, num_frameworks,
- filename TSRMLS_CC);
+ detected_framework = nr_try_detect_framework(
+ frameworks, num_frameworks, filename, filename_len TSRMLS_CC);
if (NR_FW_UNSET != detected_framework) {
NRPRG(current_framework) = detected_framework;
}
@@ -792,6 +802,8 @@ static void nr_execute_handle_framework(const nr_framework_table_t frameworks[],
}
}
+#define STR_AND_LEN(v) v, v##_len
+
/*
* Attempt to detect a framework.
* Call the appropriate enable function if we find the framework.
@@ -800,13 +812,14 @@ static void nr_execute_handle_framework(const nr_framework_table_t frameworks[],
static nrframework_t nr_try_detect_framework(
const nr_framework_table_t frameworks[],
size_t num_frameworks,
- const char* filename TSRMLS_DC) {
+ const char* filename,
+ const size_t filename_len TSRMLS_DC) {
nrframework_t detected = NR_FW_UNSET;
- char* filename_lower = nr_string_to_lowercase(filename);
size_t i;
for (i = 0; i < num_frameworks; i++) {
- if (nr_stridx(filename_lower, frameworks[i].file_to_check) >= 0) {
+ if (nr_striendswith(STR_AND_LEN(filename),
+ STR_AND_LEN(frameworks[i].file_to_check))) {
/*
* If we have a special check function and it tells us to ignore
* the file name because some other condition wasn't met, continue
@@ -830,7 +843,6 @@ static nrframework_t nr_try_detect_framework(
}
end:
- nr_free(filename_lower);
return detected;
}
@@ -869,12 +881,13 @@ static nrframework_t nr_try_force_framework(
return NR_FW_UNSET;
}
-static void nr_execute_handle_library(const char* filename TSRMLS_DC) {
- char* filename_lower = nr_string_to_lowercase(filename);
+static void nr_execute_handle_library(const char* filename,
+ const size_t filename_len TSRMLS_DC) {
size_t i;
for (i = 0; i < num_libraries; i++) {
- if (nr_stridx(filename_lower, libraries[i].file_to_check) >= 0) {
+ if (nr_striendswith(STR_AND_LEN(filename),
+ STR_AND_LEN(libraries[i].file_to_check))) {
nrl_debug(NRL_INSTRUMENT, "detected library=%s",
libraries[i].library_name);
@@ -886,18 +899,17 @@ static void nr_execute_handle_library(const char* filename TSRMLS_DC) {
}
}
}
-
- nr_free(filename_lower);
}
-static void nr_execute_handle_logging_framework(
- const char* filename TSRMLS_DC) {
- char* filename_lower = nr_string_to_lowercase(filename);
+static void nr_execute_handle_logging_framework(const char* filename,
+ const size_t filename_len
+ TSRMLS_DC) {
bool is_enabled = false;
size_t i;
for (i = 0; i < num_logging_frameworks; i++) {
- if (nr_stridx(filename_lower, logging_frameworks[i].file_to_check) >= 0) {
+ if (nr_striendswith(STR_AND_LEN(filename),
+ STR_AND_LEN(logging_frameworks[i].file_to_check))) {
nrl_debug(NRL_INSTRUMENT, "detected library=%s",
logging_frameworks[i].library_name);
@@ -912,10 +924,10 @@ static void nr_execute_handle_logging_framework(
NRPRG(txn), logging_frameworks[i].library_name, is_enabled);
}
}
-
- nr_free(filename_lower);
}
+#undef STR_AND_LEN
+
/*
* Purpose : Detect library and framework usage from a PHP file.
*
@@ -924,12 +936,20 @@ static void nr_execute_handle_logging_framework(
*
* Params : 1. Full name of a PHP file.
*/
-static void nr_php_user_instrumentation_from_file(
- const char* filename TSRMLS_DC) {
- nr_execute_handle_framework(all_frameworks, num_all_frameworks,
- filename TSRMLS_CC);
- nr_execute_handle_library(filename TSRMLS_CC);
- nr_execute_handle_logging_framework(filename TSRMLS_CC);
+static void nr_php_user_instrumentation_from_file(const char* filename,
+ const size_t filename_len
+ TSRMLS_DC) {
+ /* short circuit if filename_len is 0; a single place short circuit */
+ if (0 == filename_len) {
+ nrl_verbosedebug(NRL_AGENT,
+ "%s - received invalid filename_len for file=%s", __func__,
+ filename);
+ return;
+ }
+ nr_execute_handle_framework(all_frameworks, num_all_frameworks, filename,
+ filename_len TSRMLS_CC);
+ nr_execute_handle_library(filename, filename_len TSRMLS_CC);
+ nr_execute_handle_logging_framework(filename, filename_len TSRMLS_CC);
}
/*
@@ -940,6 +960,7 @@ static void nr_php_user_instrumentation_from_file(
static 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);
if (nrunlikely(NR_PHP_PROCESS_GLOBALS(special_flags).show_loaded_files)) {
nrl_debug(NRL_AGENT, "loaded file=" NRP_FMT, NRP_FILENAME(filename));
@@ -948,7 +969,7 @@ static void nr_php_execute_file(const zend_op_array* op_array,
/*
* Check for, and handle, frameworks and libraries.
*/
- nr_php_user_instrumentation_from_file(filename TSRMLS_CC);
+ nr_php_user_instrumentation_from_file(filename, filename_len TSRMLS_CC);
nr_txn_match_file(NRPRG(txn), filename);
@@ -1673,6 +1694,7 @@ void nr_php_user_instrumentation_from_opcache(TSRMLS_D) {
nr_php_string_hash_key_t* key_str = NULL;
zval* val = NULL;
const char* filename;
+ size_t filename_len;
status = nr_php_call(NULL, "opcache_get_status");
@@ -1717,8 +1739,9 @@ void nr_php_user_instrumentation_from_opcache(TSRMLS_D) {
(void)val;
filename = ZEND_STRING_VALUE(key_str);
+ filename_len = ZEND_STRING_LEN(key_str);
- nr_php_user_instrumentation_from_file(filename TSRMLS_CC);
+ nr_php_user_instrumentation_from_file(filename, filename_len TSRMLS_CC);
}
ZEND_HASH_FOREACH_END();
diff --git a/agent/php_includes.h b/agent/php_includes.h
index 2a350b9c1..8952fab0b 100644
--- a/agent/php_includes.h
+++ b/agent/php_includes.h
@@ -53,6 +53,7 @@
#define ZEND_8_0_X_API_NO 20200930
#define ZEND_8_1_X_API_NO 20210902
#define ZEND_8_2_X_API_NO 20220829
+#define ZEND_8_3_X_API_NO 20230831
#if ZEND_MODULE_API_NO >= ZEND_5_6_X_API_NO
#include "Zend/zend_virtual_cwd.h"
diff --git a/agent/php_minit.c b/agent/php_minit.c
index 64aea90ec..7babac21a 100644
--- a/agent/php_minit.c
+++ b/agent/php_minit.c
@@ -22,6 +22,7 @@
#include "php_vm.h"
#include "php_wrapper.h"
#include "fw_laravel.h"
+#include "fw_wordpress.h"
#include "lib_guzzle4.h"
#include "lib_guzzle6.h"
#include "nr_agent.h"
@@ -35,9 +36,6 @@
#include "util_syscalls.h"
#include "util_threads.h"
-#include "fw_laravel.h"
-#include "lib_guzzle4.h"
-
static void php_newrelic_init_globals(zend_newrelic_globals* nrg) {
if (nrunlikely(NULL == nrg)) {
return;
@@ -709,6 +707,7 @@ PHP_MINIT_FUNCTION(newrelic) {
nr_guzzle4_minit(TSRMLS_C);
nr_guzzle6_minit(TSRMLS_C);
nr_laravel_minit(TSRMLS_C);
+ nr_wordpress_minit();
nr_php_set_opcode_handlers();
nrl_debug(NRL_INIT, "MINIT processing done");
diff --git a/agent/php_mshutdown.c b/agent/php_mshutdown.c
index c290020ba..1c67d38b4 100644
--- a/agent/php_mshutdown.c
+++ b/agent/php_mshutdown.c
@@ -15,6 +15,7 @@
#include "php_vm.h"
#include "nr_agent.h"
#include "util_logging.h"
+#include "fw_wordpress.h"
#ifdef TAGS
void zm_shutdown_newrelic(void); /* ctags landing pad only */
@@ -39,6 +40,8 @@ PHP_MSHUTDOWN_FUNCTION(newrelic) {
*/
nrl_debug(NRL_INIT, "MSHUTDOWN processing started");
+ nr_wordpress_mshutdown();
+
/* restore header handler */
sapi_module.header_handler = NR_PHP_PROCESS_GLOBALS(orig_header_handler);
NR_PHP_PROCESS_GLOBALS(orig_header_handler) = NULL;
diff --git a/agent/php_newrelic.h b/agent/php_newrelic.h
index d7edaba65..82d746b78 100644
--- a/agent/php_newrelic.h
+++ b/agent/php_newrelic.h
@@ -15,6 +15,7 @@
#include "nr_txn.h"
#include "php_extension.h"
#include "util_hashmap.h"
+#include "util_matcher.h"
#include "util_vector.h"
#define PHP_NEWRELIC_EXT_NAME "newrelic"
@@ -378,13 +379,16 @@ int symfony1_in_dispatch; /* Whether we are currently within a
int symfony1_in_error404; /* Whether we are currently within a
sfError404Exception::printStackTrace() frame */
-char* wordpress_tag; /* The current WordPress tag */
-nr_regex_t* wordpress_hook_regex; /* Regex to sanitize hook names */
-nr_regex_t* wordpress_plugin_regex; /* Regex for plugin filenames */
-nr_regex_t* wordpress_theme_regex; /* Regex for theme filenames */
-nr_regex_t* wordpress_core_regex; /* Regex for plugin filenames */
-nr_hashmap_t* wordpress_file_metadata; /* Metadata for plugin and theme names
- given a filename */
+bool check_cufa; /* Whether we need to check cufa because we are
+ instrumenting hooks, or whether we can skip cufa */
+
+char* wordpress_tag; /* The current WordPress tag */
+nr_matcher_t* wordpress_plugin_matcher; /* Matcher for plugin filenames */
+nr_matcher_t* wordpress_theme_matcher; /* Matcher for theme filenames */
+nr_matcher_t* wordpress_core_matcher; /* Matcher for plugin filenames */
+nr_hashmap_t* wordpress_file_metadata; /* Metadata for plugin and theme names
+ given a filename */
+nr_hashmap_t* wordpress_clean_tag_cache; /* Cached clean tags */
char* doctrine_dql; /* The current Doctrine DQL. Only non-NULL while a Doctrine
object is on the stack. */
diff --git a/agent/php_rinit.c b/agent/php_rinit.c
index 7295821df..c8045f2e1 100644
--- a/agent/php_rinit.c
+++ b/agent/php_rinit.c
@@ -89,13 +89,7 @@ PHP_RINIT_FUNCTION(newrelic) {
nr_php_extension_instrument_rescan(NRPRG(extensions) TSRMLS_CC);
}
- /*
- * Compile regex for WordPress: includes logic for
- * hook sanitization regex.
- */
- NRPRG(wordpress_hook_regex) = nr_regex_create(
- "(^([a-z_-]+[_-])([0-9a-f_.]+[0-9][0-9a-f.]+)(_{0,1}.*)$|(.*))",
- NR_REGEX_CASELESS, 0);
+ NRPRG(check_cufa) = false;
NRPRG(mysql_last_conn) = NULL;
NRPRG(pgsql_last_conn) = NULL;
diff --git a/agent/php_rshutdown.c b/agent/php_rshutdown.c
index 184a07c10..1e68f4648 100644
--- a/agent/php_rshutdown.c
+++ b/agent/php_rshutdown.c
@@ -96,11 +96,12 @@ int nr_php_post_deactivate(void) {
nr_php_remove_transient_user_instrumentation();
nr_php_exception_filters_destroy(&NRPRG(exception_filters));
- nr_regex_destroy(&NRPRG(wordpress_plugin_regex));
- nr_regex_destroy(&NRPRG(wordpress_core_regex));
- nr_regex_destroy(&NRPRG(wordpress_hook_regex));
- nr_regex_destroy(&NRPRG(wordpress_theme_regex));
+
+ nr_matcher_destroy(&NRPRG(wordpress_plugin_matcher));
+ nr_matcher_destroy(&NRPRG(wordpress_core_matcher));
+ nr_matcher_destroy(&NRPRG(wordpress_theme_matcher));
nr_hashmap_destroy(&NRPRG(wordpress_file_metadata));
+ nr_hashmap_destroy(&NRPRG(wordpress_clean_tag_cache));
nr_free(NRPRG(mysql_last_conn));
nr_free(NRPRG(pgsql_last_conn));
diff --git a/agent/php_vm.c b/agent/php_vm.c
index 19f4bb215..4f8d7c023 100644
--- a/agent/php_vm.c
+++ b/agent/php_vm.c
@@ -108,6 +108,14 @@ static int nr_php_handle_cufa_fcall(zend_execute_data* execute_data) {
goto call_previous_and_return;
}
+ /*
+ * If we don't have instrumented hooks that require this, skip to the
+ * end.
+ */
+ if (false == NRPRG(check_cufa)) {
+ goto call_previous_and_return;
+ }
+
/*
* Since we're in the middle of a function call, the Zend Engine is actually
* only partway through constructing the new function frame. As a result, it
diff --git a/agent/tests/test_fw_wordpress.c b/agent/tests/test_fw_wordpress.c
index 7ba170e27..03b3fce85 100644
--- a/agent/tests/test_fw_wordpress.c
+++ b/agent/tests/test_fw_wordpress.c
@@ -25,55 +25,55 @@ tlib_parallel_info_t parallel_info
= {.suggested_nthreads = -1, .state_size = 0};
/*
- * This will test whether the regular expression checking works to determine
+ * This will test whether the matcher checking works to determine
* the name of a plugin from a filename when the "plugin" is a .php file.
*/
-static void test_wordpress_core_regex(TSRMLS_D) {
+static void test_wordpress_core_matcher() {
char* plugin = NULL;
/* Test with invalid input. */
- plugin = nr_php_wordpress_core_match_regex(NULL TSRMLS_CC);
+ plugin = nr_php_wordpress_core_match_matcher(NULL);
tlib_pass_if_null(
- "Wordpress function regex matching should return NULL when given NULL.",
+ "Wordpress function matcher matching should return NULL when given NULL.",
plugin);
- plugin = nr_php_wordpress_core_match_regex(
- "/wp-content/plugins/affiliatelite.php" TSRMLS_CC);
+ plugin = nr_php_wordpress_core_match_matcher(
+ "/wp-content/plugins/affiliatelite.php");
tlib_pass_if_null(
- "wordpress core regex matching should not work from the regular plugins "
+ "wordpress core matcher matching should not work from the regular plugins "
"directory",
plugin);
- plugin = nr_php_wordpress_core_match_regex(
- "/www-data/premium.wpmudev.org/wp-content/affiliatelite.php" TSRMLS_CC);
+ plugin = nr_php_wordpress_core_match_matcher(
+ "/www-data/premium.wpmudev.org/wp-content/affiliatelite.php");
tlib_pass_if_null(
- "wordpress core regex matching should not work from a non-standard "
+ "wordpress core matcher matching should not work from a non-standard "
"directory.",
plugin);
/* Test with valid input. */
- plugin = nr_php_wordpress_core_match_regex(
- "/wordpress/wordpress/wp-includes/query.php" TSRMLS_CC);
+ plugin = nr_php_wordpress_core_match_matcher(
+ "/wordpress/wordpress/wp-includes/query.php");
tlib_pass_if_not_null(
- "wordpress core regex matching should work from a standard "
+ "wordpress core matcher matching should work from a standard "
"directory.",
plugin);
tlib_pass_if_str_equal(
- "wordpress core regex matching should work from a standard "
+ "wordpress core matcher matching should work from a standard "
"directory.",
"query", plugin);
nr_free(plugin);
- plugin = nr_php_wordpress_core_match_regex(
- "/wordpress/wordpress/wp-includes/block/query.php" TSRMLS_CC);
+ plugin = nr_php_wordpress_core_match_matcher(
+ "/wordpress/wordpress/wp-includes/block/query.php");
tlib_pass_if_not_null(
- "wordpress core regex matching should work from a standard "
+ "wordpress core matcher matching should work from a standard "
"directory with a subdirectory",
plugin);
tlib_pass_if_str_equal(
- "wordpress core regex matching should work from a standard "
+ "wordpress core matcher matching should work from a standard "
"directory with a subdirectory.",
"query", plugin);
@@ -81,44 +81,44 @@ static void test_wordpress_core_regex(TSRMLS_D) {
}
/*
- * This will test whether the regular expression checking works to determine
+ * This will test whether the matcher checking works to determine
* the name of a plugin from a filename when the plugin is not a .php file
*/
-static void test_wordpress_plugin_regex(TSRMLS_D) {
+static void test_wordpress_plugin_matcher() {
char* plugin = NULL;
char* filename
= "/www-data/premium.wpmudev.org/wp-content/plugins/plugin/"
"affiliatelite.php";
/* Test with invalid input. */
- plugin = nr_php_wordpress_plugin_match_regex(NULL TSRMLS_CC);
+ plugin = nr_php_wordpress_plugin_match_matcher(NULL);
tlib_pass_if_null(
- "Wordpress plugin regex should return NULL when given NULL.", plugin);
+ "Wordpress plugin matcher should return NULL when given NULL.", plugin);
- plugin = nr_php_wordpress_plugin_match_regex(
- "/wp-content/affiliatelite.php" TSRMLS_CC);
+ plugin = nr_php_wordpress_plugin_match_matcher(
+ "/wp-content/affiliatelite.php");
tlib_pass_if_null(
- "Wordpress plugin regex should return NULL if the filename is not in the "
+ "Wordpress plugin matcher should return NULL if the filename is not in the "
"correct plugin directory.",
plugin);
/* Test with valid input. */
- plugin = nr_php_wordpress_plugin_match_regex(
- "/wp-content/plugins/affiliatelite.php" TSRMLS_CC);
+ plugin = nr_php_wordpress_plugin_match_matcher(
+ "/wp-content/plugins/affiliatelite.php");
tlib_pass_if_not_null(
- "Wordpress plugin regex should return plugin name even if the plugin is "
+ "Wordpress plugin matcher should return plugin name even if the plugin is "
"a function "
"not a directory.",
plugin);
- tlib_pass_if_str_equal("Wordpress plugin regex should work.", "affiliatelite",
+ tlib_pass_if_str_equal("Wordpress plugin matcher should work.", "affiliatelite",
plugin);
nr_free(plugin);
- plugin = nr_php_wordpress_plugin_match_regex(filename TSRMLS_CC);
- tlib_pass_if_not_null("Wordpress plugin regex should work.", plugin);
- tlib_pass_if_str_equal("Wordpress plugin regex should work.", "plugin",
+ plugin = nr_php_wordpress_plugin_match_matcher(filename);
+ tlib_pass_if_not_null("Wordpress plugin matcher should work.", plugin);
+ tlib_pass_if_str_equal("Wordpress plugin matcher should work.", "plugin",
plugin);
nr_free(plugin);
}
@@ -128,7 +128,7 @@ void test_main(void* p NRUNUSED) {
void*** tsrm_ls = NULL;
#endif /* ZTS && !PHP7 */
tlib_php_engine_create("" PTSRMLS_CC);
- test_wordpress_plugin_regex(TSRMLS_C);
- test_wordpress_core_regex(TSRMLS_C);
+ test_wordpress_plugin_matcher();
+ test_wordpress_core_matcher();
tlib_php_engine_destroy(TSRMLS_C);
}
diff --git a/agent/tests/test_internal_instrument.c b/agent/tests/test_internal_instrument.c
index de3f19619..0f4f2b3f8 100644
--- a/agent/tests/test_internal_instrument.c
+++ b/agent/tests/test_internal_instrument.c
@@ -49,6 +49,7 @@ static void test_cufa_direct(TSRMLS_D) {
tlib_php_request_start();
+ NRPRG(check_cufa) = true;
define_cufa_function_f(TSRMLS_C);
tlib_php_request_eval(
"function g() { return call_user_func_array('f', array()); }" TSRMLS_CC);
@@ -74,6 +75,7 @@ static void test_cufa_indirect(TSRMLS_D) {
tlib_php_request_start();
+ NRPRG(check_cufa) = true;
define_cufa_function_f(TSRMLS_C);
tlib_php_request_eval(
"function g() { $cufa = 'call_user_func_array'; return $cufa('f', "
diff --git a/axiom/Makefile b/axiom/Makefile
index 8dc69c1dc..c84117417 100644
--- a/axiom/Makefile
+++ b/axiom/Makefile
@@ -135,6 +135,7 @@ OBJS := \
util_json.o \
util_logging.o \
util_labels.o \
+ util_matcher.o \
util_md5.o \
util_memory.o \
util_metrics.o \
diff --git a/axiom/nr_version.c b/axiom/nr_version.c
index 30fc06d36..703447ba3 100644
--- a/axiom/nr_version.c
+++ b/axiom/nr_version.c
@@ -41,8 +41,9 @@
* narcissus 20Jun2023 (10.11)
* orchid 20Sep2023 (10.12)
* poinsettia 03Oct2023 (10.13)
+ * quince 13Nov2023 (10.14)
*/
-#define NR_CODENAME "quince"
+#define NR_CODENAME "rose"
const char* nr_version(void) {
return NR_STR2(NR_VERSION);
diff --git a/axiom/tests/.gitignore b/axiom/tests/.gitignore
index 470f8d116..e2e2db564 100644
--- a/axiom/tests/.gitignore
+++ b/axiom/tests/.gitignore
@@ -71,6 +71,7 @@ test_log_events
test_log_level
test_logging
test_logging_parallel
+test_matcher
test_math
test_memory
test_metrics
diff --git a/axiom/tests/Makefile b/axiom/tests/Makefile
index 3dc109869..0a37821bb 100644
--- a/axiom/tests/Makefile
+++ b/axiom/tests/Makefile
@@ -152,6 +152,7 @@ TESTS := \
test_log_events \
test_log_level \
test_logging \
+ test_matcher \
test_math \
test_memory \
test_metrics \
diff --git a/axiom/tests/test_matcher.c b/axiom/tests/test_matcher.c
new file mode 100644
index 000000000..9bc198b55
--- /dev/null
+++ b/axiom/tests/test_matcher.c
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2020 New Relic Corporation. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include "nr_axiom.h"
+
+#include "util_matcher.h"
+
+#include "tlib_main.h"
+
+static void test_match_multiple(void) {
+ char* found;
+ nr_matcher_t* matcher;
+
+ matcher = nr_matcher_create();
+ tlib_pass_if_bool_equal("add prefix", true,
+ nr_matcher_add_prefix(matcher, "/foo"));
+ tlib_pass_if_bool_equal("add prefix", true,
+ nr_matcher_add_prefix(matcher, "/bar//"));
+
+ tlib_pass_if_null("needle not matched", nr_matcher_match(matcher, ""));
+ tlib_pass_if_null("needle not matched", nr_matcher_match(matcher, "foo"));
+ tlib_pass_if_null("needle not matched", nr_matcher_match(matcher, "/bar"));
+
+ found = nr_matcher_match(matcher, "/foo/baz/quux");
+ tlib_pass_if_str_equal("needle match", "baz", found);
+ nr_free(found);
+
+ found = nr_matcher_match(matcher, "/foo/baz//quux");
+ tlib_pass_if_str_equal("needle match", "baz", found);
+ nr_free(found);
+
+ found = nr_matcher_match(matcher, "/bar/xxx");
+ tlib_pass_if_str_equal("needle match", "xxx", found);
+ nr_free(found);
+
+ nr_matcher_destroy(&matcher);
+}
+
+static void test_match_single(void) {
+ char* found;
+ nr_matcher_t* matcher = nr_matcher_create();
+
+ nr_matcher_add_prefix(matcher, "/foo/bar");
+
+ tlib_pass_if_null("needle not matched", nr_matcher_match(matcher, ""));
+ tlib_pass_if_null("needle not matched", nr_matcher_match(matcher, "foo"));
+ tlib_pass_if_null("needle not matched", nr_matcher_match(matcher, "/bar"));
+
+ found = nr_matcher_match(matcher, "/foo/bar/quux");
+ tlib_pass_if_str_equal("needle match", "quux", found);
+ nr_free(found);
+
+ found = nr_matcher_match(matcher, "/foo/bar//quux");
+ tlib_pass_if_str_equal("needle match", "", found);
+ nr_free(found);
+
+ found = nr_matcher_match(matcher, "/foo/bar/quux/baz");
+ tlib_pass_if_str_equal("needle match", "quux", found);
+ nr_free(found);
+
+ nr_matcher_destroy(&matcher);
+}
+
+static void test_match_ex(void) {
+ char* found;
+ int len;
+ nr_matcher_t* matcher = nr_matcher_create();
+
+ nr_matcher_add_prefix(matcher, "/foo/bar");
+ found = nr_matcher_match_ex(matcher, NR_PSTR(""), &len);
+ tlib_pass_if_null("needle not matched", found);
+ tlib_pass_if_equal("needle match len", 0, len, int, "%d")
+ nr_free(found);
+
+ found = nr_matcher_match_ex(matcher, NR_PSTR("foo"), &len);
+ tlib_pass_if_null("needle not matched", found);
+ tlib_pass_if_equal("needle match len", 0, len, int, "%d")
+ nr_free(found);
+
+ found = nr_matcher_match_ex(matcher, NR_PSTR("/bar"), &len);
+ tlib_pass_if_null("needle not matched", found);
+ tlib_pass_if_equal("needle match len", 0, len, int, "%d")
+ nr_free(found);
+
+ found = nr_matcher_match_ex(matcher, NR_PSTR("/foo/bar/quux"), &len);
+ tlib_pass_if_str_equal("needle match", "quux", found);
+ tlib_pass_if_equal("needle match len", 4, len, int, "%d")
+ nr_free(found);
+
+ found = nr_matcher_match_ex(matcher, NR_PSTR("/foo/bar//quux"), &len);
+ tlib_pass_if_str_equal("needle match", "", found);
+ tlib_pass_if_equal("needle match len", 0, len, int, "%d")
+ nr_free(found);
+
+ found = nr_matcher_match_ex(matcher, NR_PSTR("/foo/bar/quux/baz"), &len);
+ tlib_pass_if_str_equal("needle match", "quux", found);
+ tlib_pass_if_equal("needle match len", 4, len, int, "%d")
+ nr_free(found);
+
+ nr_matcher_destroy(&matcher);
+}
+
+tlib_parallel_info_t parallel_info = {.suggested_nthreads = 2, .state_size = 0};
+
+void test_main(void* p NRUNUSED) {
+ test_match_multiple();
+ test_match_single();
+ test_match_ex();
+}
diff --git a/axiom/tests/test_strings.c b/axiom/tests/test_strings.c
index ea1576e36..053d2bc60 100644
--- a/axiom/tests/test_strings.c
+++ b/axiom/tests/test_strings.c
@@ -1228,6 +1228,35 @@ static void test_str_append(void) {
nr_free(str);
}
+static void test_iendswith(void) {
+ tlib_pass_if_bool_equal("input is NULL", false,
+ nr_striendswith(NULL, 4, NR_PSTR("bar")));
+
+ tlib_pass_if_bool_equal("pattern is NULL", false,
+ nr_striendswith(NR_PSTR("foo"), NULL, 0));
+
+ tlib_pass_if_bool_equal("input is empty", false,
+ nr_striendswith(NR_PSTR(""), NR_PSTR("bar")));
+
+ tlib_pass_if_bool_equal("pattern is empty", true,
+ nr_striendswith(NR_PSTR("foo"), NR_PSTR("")));
+
+ tlib_pass_if_bool_equal("input is too short", false,
+ nr_striendswith(NR_PSTR("ar"), NR_PSTR("bar")));
+
+ tlib_pass_if_bool_equal(
+ "no match", false, nr_striendswith(NR_PSTR("foobarbaz"), NR_PSTR("bar")));
+
+ tlib_pass_if_bool_equal("not quite match", false,
+ nr_striendswith(NR_PSTR("foobarr"), NR_PSTR("bar")));
+
+ tlib_pass_if_bool_equal("suffix match", true,
+ nr_striendswith(NR_PSTR("foobar"), NR_PSTR("bar")));
+
+ tlib_pass_if_bool_equal("exact match", true,
+ nr_striendswith(NR_PSTR("bar"), NR_PSTR("bar")));
+}
+
tlib_parallel_info_t parallel_info = {.suggested_nthreads = 2, .state_size = 0};
void test_main(void* p NRUNUSED) {
@@ -1265,6 +1294,7 @@ void test_main(void* p NRUNUSED) {
test_formatf();
test_strsplit();
test_str_append();
+ test_iendswith();
/*
* character tests
diff --git a/axiom/util_matcher.c b/axiom/util_matcher.c
new file mode 100644
index 000000000..4f1ad36ec
--- /dev/null
+++ b/axiom/util_matcher.c
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2020 New Relic Corporation. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include "util_matcher.h"
+#include "util_matcher_private.h"
+
+#include "util_memory.h"
+#include "util_strings.h"
+
+typedef struct {
+ char* cp;
+ int len;
+} matcher_prefix;
+
+static void nr_matcher_prefix_dtor(void* _p, void* userdata NRUNUSED) {
+ matcher_prefix* p = (matcher_prefix*)_p;
+ nr_free(p->cp);
+ nr_free(p);
+}
+
+nr_matcher_t* nr_matcher_create(void) {
+ nr_matcher_t* matcher;
+
+ matcher = nr_malloc(sizeof(nr_matcher_t));
+ nr_vector_init(&matcher->prefixes, 0, nr_matcher_prefix_dtor, NULL);
+
+ return matcher;
+}
+
+void nr_matcher_destroy(nr_matcher_t** matcher_ptr) {
+ if (NULL == matcher_ptr || NULL == *matcher_ptr) {
+ return;
+ }
+
+ nr_vector_deinit(&((*matcher_ptr)->prefixes));
+ nr_realfree((void**)matcher_ptr);
+}
+
+bool nr_matcher_add_prefix(nr_matcher_t* matcher, const char* str) {
+ int i;
+ matcher_prefix* prefix;
+
+ if (NULL == matcher || NULL == str) {
+ return false;
+ }
+
+ if (NULL == (prefix = nr_calloc(1, sizeof(matcher_prefix)))) {
+ return false;
+ }
+ prefix->len = nr_strlen(str);
+ while (prefix->len > 0 && '/' == str[prefix->len - 1]) {
+ prefix->len--;
+ }
+
+ prefix->len += 1; // +1 for the trailing '/'
+ if (NULL == (prefix->cp = nr_malloc(prefix->len + 1))) { // +1 for the '\0'
+ nr_matcher_prefix_dtor(prefix, NULL);
+ return false;
+ }
+ for (i = 0; i < prefix->len; i++) {
+ prefix->cp[i] = nr_tolower(str[i]);
+ }
+ prefix->cp[prefix->len - 1] = '/';
+ prefix->cp[prefix->len] = '\0';
+
+ return nr_vector_push_back(&matcher->prefixes, prefix);
+}
+
+#define SET_SAFE(p, v) \
+ do { \
+ if (NULL != p) \
+ *p = (v); \
+ } while (0);
+static char* nr_matcher_match_internal(nr_matcher_t* matcher,
+ const char* input,
+ int input_len,
+ int* match_len,
+ bool core) {
+ size_t i;
+ char* input_lc;
+ char* match = NULL;
+ size_t num_prefixes;
+
+ SET_SAFE(match_len, 0);
+
+ if (NULL == matcher || NULL == input) {
+ return NULL;
+ }
+
+ num_prefixes = nr_vector_size(&matcher->prefixes);
+ input_lc = nr_string_to_lowercase(input);
+
+ for (i = 0; i < num_prefixes; i++) {
+ const char* found;
+ const matcher_prefix* prefix = nr_vector_get(&matcher->prefixes, i);
+
+ found = nr_strstr(input, prefix->cp);
+ if (found) {
+ const char* slash;
+
+ found += prefix->len;
+ if (true == core) {
+ slash = nr_strrchr(found, '/');
+ } else {
+ slash = nr_strchr(found, '/');
+ }
+ if (NULL == slash) {
+ match = nr_strdup(found);
+ SET_SAFE(match_len, input_len - (found - input));
+ } else {
+ if (true == core) {
+ const char* offset = input + input_len;
+ match = nr_strndup(slash + 1, offset - slash);
+ SET_SAFE(match_len, offset - (slash + 1));
+ } else {
+ match = nr_strndup(found, slash - found);
+ SET_SAFE(match_len, slash - found);
+ }
+ }
+ break;
+ }
+ }
+
+ nr_free(input_lc);
+ return match;
+}
+
+char* nr_matcher_match_ex(nr_matcher_t* matcher,
+ const char* input,
+ int input_len,
+ int* match_len) {
+ return nr_matcher_match_internal(matcher, input, input_len, match_len, false);
+}
+
+char* nr_matcher_match(nr_matcher_t* matcher, const char* input) {
+ return nr_matcher_match_internal(matcher, input, nr_strlen(input), NULL,
+ false);
+}
+
+char* nr_matcher_match_r_ex(nr_matcher_t* matcher,
+ const char* input,
+ int input_len,
+ int* match_len) {
+ return nr_matcher_match_internal(matcher, input, input_len, match_len, true);
+}
+
+char* nr_matcher_match_r(nr_matcher_t* matcher, const char* input) {
+ return nr_matcher_match_internal(matcher, input, nr_strlen(input), NULL,
+ true);
+}
diff --git a/axiom/util_matcher.h b/axiom/util_matcher.h
new file mode 100644
index 000000000..2e4b12a53
--- /dev/null
+++ b/axiom/util_matcher.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2020 New Relic Corporation. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#ifndef UTIL_MATCHER_HDR
+#define UTIL_MATCHER_HDR
+
+#include
+
+typedef struct _nr_matcher_t nr_matcher_t;
+
+extern nr_matcher_t* nr_matcher_create(void);
+
+extern void nr_matcher_destroy(nr_matcher_t** matcher_ptr);
+
+extern bool nr_matcher_add_prefix(nr_matcher_t* matcher, const char* prefix);
+
+/*
+ * Return a substring from an input string using a matcher definition from the
+ * left side of the string
+ */
+extern char* nr_matcher_match_ex(nr_matcher_t* matcher,
+ const char* input,
+ int input_len,
+ int* match_len);
+extern char* nr_matcher_match(nr_matcher_t* matcher, const char* input);
+
+/*
+ * Return a substring from an input string using a matcher definition from the
+ * right side of the string
+ */
+extern char* nr_matcher_match_r_ex(nr_matcher_t* matcher,
+ const char* input,
+ int input_len,
+ int* match_len);
+extern char* nr_matcher_match_r(nr_matcher_t* matcher, const char* input);
+
+#endif
diff --git a/axiom/util_matcher_private.h b/axiom/util_matcher_private.h
new file mode 100644
index 000000000..e6c7f3e79
--- /dev/null
+++ b/axiom/util_matcher_private.h
@@ -0,0 +1,15 @@
+/*
+ * Copyright 2020 New Relic Corporation. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#ifndef UTIL_MATCHER_PRIVATE_HDR
+#define UTIL_MATCHER_PRIVATE_HDR
+
+#include "util_vector.h"
+
+typedef struct _nr_matcher_t {
+ nr_vector_t prefixes;
+} nr_matcher_t;
+
+#endif /* UTIL_MATCHER_PRIVATE_HDR */
diff --git a/axiom/util_strings.h b/axiom/util_strings.h
index c1d61e0b4..5da515cab 100644
--- a/axiom/util_strings.h
+++ b/axiom/util_strings.h
@@ -16,6 +16,7 @@
#include "nr_axiom.h"
+#include
#include
#include "util_object.h"
@@ -438,4 +439,36 @@ static inline int nr_toupper(int c) {
return c;
}
+/*
+ * Purpose : Checks whether a string ends with the specified pattern. Note that
+ * nr_striendswith is case-insensitive.
+ *
+ * Params : 1. The input string to examine.
+ * 2. The input string length.
+ * 3. The pattern to look for at the end of the string.
+ * 4. The pattern length.
+ *
+ * Returns : The true if input string ends with the pattern or false otherwise.
+ */
+static inline bool nr_striendswith(const char* s,
+ const size_t slen,
+ const char* pattern,
+ const size_t pattern_len) {
+ const char* suffix;
+
+ if (slen < pattern_len) {
+ /* input shorter than pattern */
+ return false;
+ }
+
+ if (NULL == s) {
+ /* invalid input */
+ return false;
+ }
+
+ /* compare input's suffix with the pattern and return result */
+ suffix = s + (slen - pattern_len);
+ return 0 == nr_stricmp(suffix, pattern);
+}
+
#endif /* UTIL_STRINGS_HDR */
diff --git a/docs/development.md b/docs/development.md
index 64157900b..c85755903 100644
--- a/docs/development.md
+++ b/docs/development.md
@@ -58,7 +58,7 @@ _(most operating systems package these with `-dev` or `-devel` suffixes)_
### PHP
-The PHP agent supports PHP versions `7.0`, `7.1`, `7.2`, `7.3`, `7.4`,`8.0`, `8.1`, and `8.2`.
+The PHP agent supports PHP versions `7.0`, `7.1`, `7.2`, `7.3`, `7.4`,`8.0`, `8.1`, '8.2', and `8.3`.
## Build the PHP Agent
diff --git a/files/Dockerfile b/files/Dockerfile
index b0bec8bdd..f13cd367b 100644
--- a/files/Dockerfile
+++ b/files/Dockerfile
@@ -9,7 +9,7 @@
ARG PHP_VER
-FROM php:${PHP_VER:-8.2}
+FROM php:${PHP_VER:-8.3}
RUN docker-php-source extract
diff --git a/make/php_versions.mk b/make/php_versions.mk
index c3477bac9..0629bfabd 100644
--- a/make/php_versions.mk
+++ b/make/php_versions.mk
@@ -3,4 +3,4 @@
# SPDX-License-Identifier: Apache-2.0
#
-PHP_VERSION_LIST=$${PHPS:-8.2 8.1 8.0 7.4 7.3 7.2 7.1 7.0}
+PHP_VERSION_LIST=$${PHPS:-8.3 8.2 8.1 8.0 7.4 7.3 7.2 7.1 7.0}
diff --git a/make/release.mk b/make/release.mk
index 0f3ce7e0a..6bb9eba61 100644
--- a/make/release.mk
+++ b/make/release.mk
@@ -163,6 +163,7 @@ release-$1-zts: Makefile agent | releases/$$(RELEASE_OS)/agent/$$(RELEASE_ARCH)/
endef
+$(eval $(call RELEASE_AGENT_TARGET,8.3,20230831))
$(eval $(call RELEASE_AGENT_TARGET,8.2,20220829))
$(eval $(call RELEASE_AGENT_TARGET,8.1,20210902))
$(eval $(call RELEASE_AGENT_TARGET,8.0,20200930))
diff --git a/src/newrelic/integration/parse.go b/src/newrelic/integration/parse.go
index 554972556..f464f7736 100644
--- a/src/newrelic/integration/parse.go
+++ b/src/newrelic/integration/parse.go
@@ -19,28 +19,30 @@ import (
var (
Directives = map[string]func(*Test, []byte) error{
- "ENVIRONMENT": parseEnv,
- "HEADERS": parseHeaders,
- "SKIPIF": parseRawSkipIf,
- "INI": parseSettings,
- "CONFIG": parseConfig,
- "DESCRIPTION": parseDescription,
- "EXPECT_ANALYTICS_EVENTS": parseAnalyticEvents,
- "EXPECT_CUSTOM_EVENTS": parseCustomEvents,
- "EXPECT_ERROR_EVENTS": parseErrorEvents,
- "EXPECT_SPAN_EVENTS": parseSpanEvents,
- "EXPECT_SPAN_EVENTS_LIKE": parseSpanEventsLike,
- "EXPECT_LOG_EVENTS": parseLogEvents,
- "EXPECT_METRICS": parseMetrics,
- "EXPECT": parseExpect,
- "EXPECT_REGEX": parseExpectRegex,
- "EXPECT_SCRUBBED": parseExpectScrubbed,
- "EXPECT_HARVEST": parseExpectHarvest,
- "EXPECT_SLOW_SQLS": parseSlowSQLs,
- "EXPECT_TRACED_ERRORS": parseTracedErrors,
- "EXPECT_TXN_TRACES": parseTxnTraces,
- "EXPECT_RESPONSE_HEADERS": parseResponseHeaders,
- "XFAIL": parseXFail,
+ "ENVIRONMENT": parseEnv,
+ "HEADERS": parseHeaders,
+ "SKIPIF": parseRawSkipIf,
+ "INI": parseSettings,
+ "CONFIG": parseConfig,
+ "DESCRIPTION": parseDescription,
+ "EXPECT_ANALYTICS_EVENTS": parseAnalyticEvents,
+ "EXPECT_CUSTOM_EVENTS": parseCustomEvents,
+ "EXPECT_ERROR_EVENTS": parseErrorEvents,
+ "EXPECT_SPAN_EVENTS": parseSpanEvents,
+ "EXPECT_SPAN_EVENTS_LIKE": parseSpanEventsLike,
+ "EXPECT_LOG_EVENTS": parseLogEvents,
+ "EXPECT_METRICS": parseMetrics,
+ "EXPECT_METRICS_EXIST": parseMetricsExist,
+ "EXPECT_METRICS_DONT_EXIST": parseMetricsDontExist,
+ "EXPECT": parseExpect,
+ "EXPECT_REGEX": parseExpectRegex,
+ "EXPECT_SCRUBBED": parseExpectScrubbed,
+ "EXPECT_HARVEST": parseExpectHarvest,
+ "EXPECT_SLOW_SQLS": parseSlowSQLs,
+ "EXPECT_TRACED_ERRORS": parseTracedErrors,
+ "EXPECT_TXN_TRACES": parseTxnTraces,
+ "EXPECT_RESPONSE_HEADERS": parseResponseHeaders,
+ "XFAIL": parseXFail,
}
)
@@ -224,6 +226,14 @@ func parseMetrics(test *Test, content []byte) error {
test.metrics = content
return nil
}
+func parseMetricsExist(test *Test, content []byte) error {
+ test.metricsExist = content
+ return nil
+}
+func parseMetricsDontExist(test *Test, content []byte) error {
+ test.metricsDontExist = content
+ return nil
+}
func parseSlowSQLs(test *Test, content []byte) error {
test.slowSQLs = content
return nil
diff --git a/src/newrelic/integration/test.go b/src/newrelic/integration/test.go
index 4ce126a33..a82948e15 100644
--- a/src/newrelic/integration/test.go
+++ b/src/newrelic/integration/test.go
@@ -29,16 +29,18 @@ type Test struct {
Desc string
// Expected Data
- analyticEvents []byte
- customEvents []byte
- errorEvents []byte
- spanEvents []byte
- spanEventsLike []byte
- logEvents []byte
- metrics []byte
- slowSQLs []byte
- tracedErrors []byte
- txnTraces []byte
+ analyticEvents []byte
+ customEvents []byte
+ errorEvents []byte
+ spanEvents []byte
+ spanEventsLike []byte
+ logEvents []byte
+ metrics []byte
+ metricsExist []byte
+ metricsDontExist []byte
+ slowSQLs []byte
+ tracedErrors []byte
+ txnTraces []byte
// Expected Output
expect []byte
expectRegex []byte
@@ -557,6 +559,24 @@ func (t *Test) Compare(harvest *newrelic.Harvest) {
return
}
+ if nil != t.metricsExist {
+ for _, name := range strings.Split(strings.TrimSpace(string(t.metricsExist)), "\n") {
+ name = strings.TrimSpace(name)
+ if !harvest.Metrics.Has(name) {
+ t.Fail(fmt.Errorf("metric does not exist: %s\n\nactual metric table: %s", name, harvest.Metrics.DebugJSON()))
+ }
+ }
+ }
+
+ if nil != t.metricsDontExist {
+ for _, name := range strings.Split(strings.TrimSpace(string(t.metricsDontExist)), "\n") {
+ name = strings.TrimSpace(name)
+ if harvest.Metrics.Has(name) {
+ t.Fail(fmt.Errorf("unexpected metric in harvest: %s\n\nactual metric table: %s", name, harvest.Metrics.DebugJSON()))
+ }
+ }
+ }
+
// if we expect a harvest and these is not, then we run our tests as per normal
t.compareResponseHeaders()
diff --git a/src/newrelic/metrics.go b/src/newrelic/metrics.go
index 3a5e7cee0..28a78620c 100644
--- a/src/newrelic/metrics.go
+++ b/src/newrelic/metrics.go
@@ -340,6 +340,13 @@ func (mt *MetricTable) Empty() bool {
return 0 == mt.count
}
+// Has returns true if the given metric exists in the metric table (regardless
+// of scope).
+func (mt *MetricTable) Has(name string) bool {
+ _, ok := mt.metrics[name]
+ return ok
+}
+
// Data marshals the collection to JSON according to the schema expected
// by the collector.
func (mt *MetricTable) Data(id AgentRunID, harvestStart time.Time) ([]byte,
diff --git a/src/newrelic/metrics_test.go b/src/newrelic/metrics_test.go
index 1aee056dc..1c29e6eaa 100644
--- a/src/newrelic/metrics_test.go
+++ b/src/newrelic/metrics_test.go
@@ -301,3 +301,21 @@ func BenchmarkMetricTableCollectorJSON(b *testing.B) {
mt.CollectorJSON(AgentRunID("12345"), now)
}
}
+
+func TestMetricTableHas(t *testing.T) {
+ mt := NewMetricTable(20, start)
+ mt.AddCount("foo", "", 1, 0)
+ mt.AddCount("bar", "quux", 1, 0)
+
+ if mt.Has("quux") {
+ t.Fatal("non-existent metric is reported as existing")
+ }
+
+ if !mt.Has("foo") {
+ t.Fatal("unscoped metric is reported as missing")
+ }
+
+ if !mt.Has("bar") {
+ t.Fatal("scoped metric is reported as missing")
+ }
+}
diff --git a/tests/include/helpers.php b/tests/include/helpers.php
index d528ececb..92e28a0f4 100644
--- a/tests/include/helpers.php
+++ b/tests/include/helpers.php
@@ -31,8 +31,8 @@ function force_error() {
* A user function has to be called to force a transaction trace. PHP 7.1
* includes dead code elimination, which means that we have to actually do
* something that can't be eliminated: re-setting the error reporting level to
- * what it already is will do the job.
+ * what it already is will do the job. Additionally force non-zero duration for the segment not to be dropped.
*/
function force_transaction_trace() {
- error_reporting(error_reporting());
+ error_reporting(error_reporting()); time_nanosleep(0, 50000); // 50usec should be enough
}
diff --git a/tests/integration/external/guzzle5/test_spans_external.php b/tests/integration/external/guzzle5/test_spans_external.php
index c32e4ec34..c03af59cf 100644
--- a/tests/integration/external/guzzle5/test_spans_external.php
+++ b/tests/integration/external/guzzle5/test_spans_external.php
@@ -26,14 +26,8 @@
/*EXPECT_RESPONSE_HEADERS
*/
-/*EXPECT_SPAN_EVENTS
+/*EXPECT_SPAN_EVENTS_LIKE
[
- "?? agent run id",
- {
- "reservoir_size": 10000,
- "events_seen": 4
- },
- [
[
{
"traceId": "??",
@@ -52,28 +46,6 @@
{},
{}
],
- [
- {
- "traceId": "??",
- "duration": "??",
- "transactionId": "??",
- "name": "Custom\/GuzzleHttp\\Client::__construct",
- "guid": "??",
- "type": "Span",
- "category": "generic",
- "priority": "??",
- "sampled": true,
- "parentId": "??",
- "timestamp": "??"
- },
- {},
- {
- "code.lineno": "??",
- "code.namespace": "GuzzleHttp\\Client",
- "code.filepath": "??",
- "code.function": "__construct"
- }
- ],
[
{
"traceId": "??",
@@ -120,7 +92,6 @@
"http.statusCode": 200
}
]
- ]
]
*/
diff --git a/tests/integration/external/guzzle5/test_spans_external.php5.php b/tests/integration/external/guzzle5/test_spans_external.php5.php
index 6d8f0b1fa..509e96964 100644
--- a/tests/integration/external/guzzle5/test_spans_external.php5.php
+++ b/tests/integration/external/guzzle5/test_spans_external.php5.php
@@ -23,14 +23,8 @@
/*EXPECT_RESPONSE_HEADERS
*/
-/*EXPECT_SPAN_EVENTS
+/*EXPECT_SPAN_EVENTS_LIKE
[
- "?? agent run id",
- {
- "reservoir_size": 10000,
- "events_seen": 4
- },
- [
[
{
"traceId": "??",
@@ -49,23 +43,6 @@
{},
{}
],
- [
- {
- "traceId": "??",
- "duration": "??",
- "transactionId": "??",
- "name": "Custom\/GuzzleHttp\\Client::__construct",
- "guid": "??",
- "type": "Span",
- "category": "generic",
- "priority": "??",
- "sampled": true,
- "parentId": "??",
- "timestamp": "??"
- },
- {},
- {}
- ],
[
{
"traceId": "??",
@@ -112,7 +89,6 @@
"http.statusCode": 200
}
]
- ]
]
*/
diff --git a/tests/integration/external/guzzle6/test_spans_are_created_correctly.php b/tests/integration/external/guzzle6/test_spans_are_created_correctly.php
index 2692781a4..0c9d0994f 100644
--- a/tests/integration/external/guzzle6/test_spans_are_created_correctly.php
+++ b/tests/integration/external/guzzle6/test_spans_are_created_correctly.php
@@ -24,14 +24,8 @@
*/
-/*EXPECT_SPAN_EVENTS
+/*EXPECT_SPAN_EVENTS_LIKE
[
- "?? agent run id",
- {
- "reservoir_size": 10000,
- "events_seen": 3
- },
- [
[
{
"traceId": "??",
@@ -50,28 +44,6 @@
{},
{}
],
- [
- {
- "traceId": "??",
- "duration": "??",
- "transactionId": "??",
- "name": "Custom\/GuzzleHttp\\Client::__construct",
- "guid": "??",
- "type": "Span",
- "category": "generic",
- "priority": "??",
- "sampled": true,
- "parentId": "??",
- "timestamp": "??"
- },
- {},
- {
- "code.lineno": "??",
- "code.namespace": "GuzzleHttp\\Client",
- "code.filepath": "??",
- "code.function": "__construct"
- }
- ],
[
{
"traceId": "??",
@@ -95,7 +67,6 @@
"http.statusCode": 200
}
]
- ]
]
*/
diff --git a/tests/integration/external/guzzle6/test_spans_are_created_correctly.php5.php b/tests/integration/external/guzzle6/test_spans_are_created_correctly.php5.php
index 1520144f6..82b9b9911 100644
--- a/tests/integration/external/guzzle6/test_spans_are_created_correctly.php5.php
+++ b/tests/integration/external/guzzle6/test_spans_are_created_correctly.php5.php
@@ -21,14 +21,8 @@
*/
-/*EXPECT_SPAN_EVENTS
+/*EXPECT_SPAN_EVENTS_LIKE
[
- "?? agent run id",
- {
- "reservoir_size": 10000,
- "events_seen": 3
- },
- [
[
{
"traceId": "??",
@@ -47,23 +41,6 @@
{},
{}
],
- [
- {
- "traceId": "??",
- "duration": "??",
- "transactionId": "??",
- "name": "Custom\/GuzzleHttp\\Client::__construct",
- "guid": "??",
- "type": "Span",
- "category": "generic",
- "priority": "??",
- "sampled": true,
- "parentId": "??",
- "timestamp": "??"
- },
- {},
- {}
- ],
[
{
"traceId": "??",
@@ -87,7 +64,6 @@
"http.statusCode": 200
}
]
- ]
]
*/
diff --git a/tests/integration/external/guzzle7/test_spans_are_created_correctly.php b/tests/integration/external/guzzle7/test_spans_are_created_correctly.php
index 58d3825e0..87388da64 100644
--- a/tests/integration/external/guzzle7/test_spans_are_created_correctly.php
+++ b/tests/integration/external/guzzle7/test_spans_are_created_correctly.php
@@ -20,14 +20,8 @@
*/
-/*EXPECT_SPAN_EVENTS
+/*EXPECT_SPAN_EVENTS_LIKE
[
- "?? agent run id",
- {
- "reservoir_size": 10000,
- "events_seen": 3
- },
- [
[
{
"traceId": "??",
@@ -46,28 +40,6 @@
{},
{}
],
- [
- {
- "traceId": "??",
- "duration": "??",
- "transactionId": "??",
- "name": "Custom\/GuzzleHttp\\Client::__construct",
- "guid": "??",
- "type": "Span",
- "category": "generic",
- "priority": "??",
- "sampled": true,
- "parentId": "??",
- "timestamp": "??"
- },
- {},
- {
- "code.lineno": "??",
- "code.namespace": "GuzzleHttp\\Client",
- "code.filepath": "??",
- "code.function": "__construct"
- }
- ],
[
{
"traceId": "??",
@@ -91,7 +63,6 @@
"http.statusCode": 200
}
]
- ]
]
*/
diff --git a/tests/integration/frameworks/wordpress/test_wordpress_transaction_name_hooks_off.php b/tests/integration/frameworks/wordpress/test_wordpress_transaction_name_hooks_off.php
new file mode 100644
index 000000000..f2b9285e5
--- /dev/null
+++ b/tests/integration/frameworks/wordpress/test_wordpress_transaction_name_hooks_off.php
@@ -0,0 +1,55 @@
+ 'mysql',
'collection' => 'table',
diff --git a/tests/integration/ini/test_transaction_tracer_max_segments_with_datastore.php5.php b/tests/integration/ini/test_transaction_tracer_max_segments_with_datastore.php5.php
index fb7b1e706..c27e5d3be 100644
--- a/tests/integration/ini/test_transaction_tracer_max_segments_with_datastore.php5.php
+++ b/tests/integration/ini/test_transaction_tracer_max_segments_with_datastore.php5.php
@@ -112,7 +112,7 @@
function my_function(){
- printf('');
+ time_nanosleep(0, 50000); // force non-zero duration for the segment not to be dropped; duration needs to be shorter than datastore segment
}
newrelic_add_custom_tracer("my_function");
@@ -122,7 +122,7 @@ function my_function(){
my_function();
newrelic_record_datastore_segment(function () {
- return 42;
+ time_nanosleep(0, 70000); return 42; // force non-zero duration for the segment not to be dropped; duration needs to be longer than user func segment
}, array(
'product' => 'mysql',
'collection' => 'table',
diff --git a/tests/integration/logging/analog/test_supportability_metric.php b/tests/integration/logging/analog/test_supportability_metric.php
index c0c5ce644..f3cfb4477 100644
--- a/tests/integration/logging/analog/test_supportability_metric.php
+++ b/tests/integration/logging/analog/test_supportability_metric.php
@@ -16,25 +16,9 @@
newrelic.application_logging.enabled = true
*/
-/*EXPECT_METRICS
-[
- "?? agent run id",
- "?? timeframe start",
- "?? timeframe stop",
- [
- [{"name":"DurationByCaller/Unknown/Unknown/Unknown/Unknown/all"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"OtherTransaction/all"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"OtherTransaction/php__FILE__"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"OtherTransactionTotalTime"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"OtherTransactionTotalTime/php__FILE__"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"Supportability/Logging/PHP/Analog/disabled"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"Supportability/library/Analog/detected"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"Supportability/Logging/Forwarding/PHP/enabled"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"Supportability/Logging/Metrics/PHP/enabled"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"Supportability/Logging/LocalDecorating/PHP/disabled"}, [1, "??", "??", "??", "??", "??"]]
- ]
-]
+/*EXPECT_METRICS_EXIST
+Supportability/library/Analog/detected
+Supportability/Logging/PHP/Analog/disabled
*/
diff --git a/tests/integration/logging/cakephp-log/test_supportability_metric.php b/tests/integration/logging/cakephp-log/test_supportability_metric.php
index 777dae825..93bc9e01e 100644
--- a/tests/integration/logging/cakephp-log/test_supportability_metric.php
+++ b/tests/integration/logging/cakephp-log/test_supportability_metric.php
@@ -16,26 +16,9 @@
newrelic.application_logging.enabled = true
*/
-/*EXPECT_METRICS
-[
- "?? agent run id",
- "?? timeframe start",
- "?? timeframe stop",
- [
- [{"name":"DurationByCaller/Unknown/Unknown/Unknown/Unknown/all"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"OtherTransaction/all"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"OtherTransaction/php__FILE__"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"OtherTransactionTotalTime"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"OtherTransactionTotalTime/php__FILE__"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"Supportability/Logging/PHP/cakephp-log/disabled"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"Supportability/library/cakephp-log/detected"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"Supportability/Logging/Forwarding/PHP/enabled"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"Supportability/Logging/Metrics/PHP/enabled"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"Supportability/Logging/LocalDecorating/PHP/disabled"}, [1, "??", "??", "??", "??", "??"]]
- ]
-]
+/*EXPECT_METRICS_EXIST
+Supportability/library/cakephp-log/detected
+Supportability/Logging/PHP/cakephp-log/disabled
*/
-
require_once(realpath(dirname(__FILE__)) . '/vendor/cakephp/log/Log.php');
diff --git a/tests/integration/logging/consolidation-log/test_supportability_metric.php b/tests/integration/logging/consolidation-log/test_supportability_metric.php
index e41529604..0eac6dfe2 100644
--- a/tests/integration/logging/consolidation-log/test_supportability_metric.php
+++ b/tests/integration/logging/consolidation-log/test_supportability_metric.php
@@ -16,26 +16,9 @@
newrelic.application_logging.enabled = true
*/
-/*EXPECT_METRICS
-[
- "?? agent run id",
- "?? timeframe start",
- "?? timeframe stop",
- [
- [{"name":"DurationByCaller/Unknown/Unknown/Unknown/Unknown/all"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"OtherTransaction/all"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"OtherTransaction/php__FILE__"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"OtherTransactionTotalTime"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"OtherTransactionTotalTime/php__FILE__"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"Supportability/Logging/PHP/Consolidation/Log/disabled"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"Supportability/library/Consolidation/Log/detected"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"Supportability/Logging/Forwarding/PHP/enabled"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"Supportability/Logging/Metrics/PHP/enabled"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"Supportability/Logging/LocalDecorating/PHP/disabled"}, [1, "??", "??", "??", "??", "??"]]
- ]
-]
+/*EXPECT_METRICS_EXIST
+Supportability/library/Consolidation/Log/detected
+Supportability/Logging/PHP/Consolidation/Log/disabled
*/
-
require_once(realpath(dirname(__FILE__)) . '/vendor/consolidation/log/src/Logger.php');
diff --git a/tests/integration/logging/laminas-log/test_supportability_metric.php b/tests/integration/logging/laminas-log/test_supportability_metric.php
index 104f5d274..ddf7d276a 100644
--- a/tests/integration/logging/laminas-log/test_supportability_metric.php
+++ b/tests/integration/logging/laminas-log/test_supportability_metric.php
@@ -16,26 +16,9 @@
newrelic.application_logging.enabled = true
*/
-/*EXPECT_METRICS
-[
- "?? agent run id",
- "?? timeframe start",
- "?? timeframe stop",
- [
- [{"name":"DurationByCaller/Unknown/Unknown/Unknown/Unknown/all"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"OtherTransaction/all"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"OtherTransaction/php__FILE__"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"OtherTransactionTotalTime"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"OtherTransactionTotalTime/php__FILE__"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"Supportability/Logging/PHP/laminas-log/disabled"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"Supportability/library/laminas-log/detected"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"Supportability/Logging/Forwarding/PHP/enabled"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"Supportability/Logging/Metrics/PHP/enabled"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"Supportability/Logging/LocalDecorating/PHP/disabled"}, [1, "??", "??", "??", "??", "??"]]
- ]
-]
+/*EXPECT_METRICS_EXIST
+Supportability/library/laminas-log/detected
+Supportability/Logging/PHP/laminas-log/disabled
*/
-
require_once(realpath(dirname(__FILE__)) . '/vendor/laminas/laminas-log/src/Logger.php');
diff --git a/tests/integration/redis/test_instance_list.php b/tests/integration/redis/test_instance_list.php
index e925de3e9..d9e7deca6 100644
--- a/tests/integration/redis/test_instance_list.php
+++ b/tests/integration/redis/test_instance_list.php
@@ -89,8 +89,7 @@ function test_redis() {
tap_equal(2, $redis->rPush($key, 'C'), 'append C');
tap_equal(3, $redis->lPush($key, 'A'), 'prepend A');
- /* Redis->lGet is deprecated, but use it once to verify it works */
- tap_equal('A', @$redis->lGet($key, 0), 'retrieve element 0');
+ tap_equal('A', $redis->lIndex($key, 0), 'retrieve element 0');
tap_equal('B', $redis->lIndex($key, 1), 'retrieve element 1');
tap_equal('C', $redis->lIndex($key, 2), 'retrieve element 2');
tap_equal('C', $redis->lIndex($key, -1), 'retrieve last element');
@@ -119,8 +118,7 @@ function test_redis() {
tap_equal(4, $redis->rpush($key, 'A', 'B', 'B', 'C'), 'add new elements');
- /* Redis->lRemove is depreacted, but use it once to verity it works */
- tap_equal(1, @$redis->lRemove($key, 'B', 1), 'remove first occurence of B');
+ tap_equal(1, @$redis->lRem($key, 'B', 1), 'remove first occurence of B');
tap_equal(['A', 'B', 'C'], $redis->lrange($key, 0, -1), 'verify list elements');
tap_equal(0, $redis->lRem($key, 'NOT_IN_LIST', 1), 'remove missing element');
@@ -146,7 +144,6 @@ function test_redis() {
'Datastore/operation/Redis/del',
'Datastore/operation/Redis/exists',
'Datastore/operation/Redis/expire',
- 'Datastore/operation/Redis/lget',
'Datastore/operation/Redis/lindex',
'Datastore/operation/Redis/linsert',
'Datastore/operation/Redis/llen',
@@ -155,7 +152,6 @@ function test_redis() {
'Datastore/operation/Redis/lpushx',
'Datastore/operation/Redis/lrange',
'Datastore/operation/Redis/lrem',
- 'Datastore/operation/Redis/lremove',
'Datastore/operation/Redis/lset',
'Datastore/operation/Redis/ltrim',
'Datastore/operation/Redis/rpop',
diff --git a/tests/integration/redis/test_lget.php b/tests/integration/redis/test_lget.php
new file mode 100644
index 000000000..a0c9ed88d
--- /dev/null
+++ b/tests/integration/redis/test_lget.php
@@ -0,0 +1,67 @@
+')) {
+ die("skip: Redis <= 5.3.7 required\n");
+}
+
+require("skipif.inc");
+*/
+
+/*INI
+newrelic.datastore_tracer.database_name_reporting.enabled = 1
+newrelic.datastore_tracer.instance_reporting.enabled = 1
+*/
+
+/*EXPECT
+ok - append B
+ok - append C
+ok - prepend A
+ok - retrieve element 0
+*/
+
+/*EXPECT_METRICS_EXIST
+Datastore/operation/Redis/lget
+*/
+
+require_once(realpath (dirname ( __FILE__ )) . '/../../include/helpers.php');
+require_once(realpath (dirname ( __FILE__ )) . '/../../include/integration.php');
+require_once(realpath (dirname ( __FILE__ )) . '/../../include/tap.php');
+require_once(realpath (dirname ( __FILE__ )) . '/redis.inc');
+
+function test_redis() {
+ global $REDIS_HOST, $REDIS_PORT;
+
+ $redis = new Redis();
+ $redis->connect($REDIS_HOST, $REDIS_PORT);
+
+ /* generate unique key names to use for this test run */
+ $key = randstr(16);
+ $key2 = "{$key}_b";
+ if ($redis->exists([$key, $key2])) {
+ die("skip: key(s) already exist: $key, $key2\n");
+ }
+
+ /* Ensure the keys don't persist (too much) longer than the test. */
+ $redis->expire($key, 30 /* seconds */);
+ $redis->expire($key2, 30 /* seconds */);
+
+ tap_equal(1, $redis->rPush($key, 'B'), 'append B');
+ tap_equal(2, $redis->rPush($key, 'C'), 'append C');
+ tap_equal(3, $redis->lPush($key, 'A'), 'prepend A');
+
+ /* Redis->lGet is deprecated, but use it once to verify it works */
+ tap_equal('A', @$redis->lGet($key, 0), 'retrieve element 0');
+}
+
+test_redis();
diff --git a/tests/integration/redis/test_list.php b/tests/integration/redis/test_list.php
index 8ed77cc67..185e7cea0 100644
--- a/tests/integration/redis/test_list.php
+++ b/tests/integration/redis/test_list.php
@@ -82,12 +82,9 @@
[{"name":"Datastore/operation/Redis/expire"}, [2, "??", "??", "??", "??", "??"]],
[{"name":"Datastore/operation/Redis/expire",
"scope":"OtherTransaction/php__FILE__"}, [2, "??", "??", "??", "??", "??"]],
- [{"name":"Datastore/operation/Redis/lget"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"Datastore/operation/Redis/lget",
- "scope":"OtherTransaction/php__FILE__"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"Datastore/operation/Redis/lindex"}, [8, "??", "??", "??", "??", "??"]],
+ [{"name":"Datastore/operation/Redis/lindex"}, [9, "??", "??", "??", "??", "??"]],
[{"name":"Datastore/operation/Redis/lindex",
- "scope":"OtherTransaction/php__FILE__"}, [8, "??", "??", "??", "??", "??"]],
+ "scope":"OtherTransaction/php__FILE__"}, [9, "??", "??", "??", "??", "??"]],
[{"name":"Datastore/operation/Redis/linsert"}, [2, "??", "??", "??", "??", "??"]],
[{"name":"Datastore/operation/Redis/linsert",
"scope":"OtherTransaction/php__FILE__"}, [2, "??", "??", "??", "??", "??"]],
@@ -106,12 +103,9 @@
[{"name":"Datastore/operation/Redis/lrange"}, [4, "??", "??", "??", "??", "??"]],
[{"name":"Datastore/operation/Redis/lrange",
"scope":"OtherTransaction/php__FILE__"}, [4, "??", "??", "??", "??", "??"]],
- [{"name":"Datastore/operation/Redis/lrem"}, [1, "??", "??", "??", "??", "??"]],
+ [{"name":"Datastore/operation/Redis/lrem"}, [2, "??", "??", "??", "??", "??"]],
[{"name":"Datastore/operation/Redis/lrem",
- "scope":"OtherTransaction/php__FILE__"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"Datastore/operation/Redis/lremove"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"Datastore/operation/Redis/lremove",
- "scope":"OtherTransaction/php__FILE__"}, [1, "??", "??", "??", "??", "??"]],
+ "scope":"OtherTransaction/php__FILE__"}, [2, "??", "??", "??", "??", "??"]],
[{"name":"Datastore/operation/Redis/lset"}, [1, "??", "??", "??", "??", "??"]],
[{"name":"Datastore/operation/Redis/lset",
"scope":"OtherTransaction/php__FILE__"}, [1, "??", "??", "??", "??", "??"]],
@@ -141,9 +135,6 @@
]
*/
-
-
-
require_once(realpath (dirname ( __FILE__ )) . '/../../include/helpers.php');
require_once(realpath (dirname ( __FILE__ )) . '/../../include/tap.php');
require_once(realpath (dirname ( __FILE__ )) . '/redis.inc');
@@ -169,8 +160,7 @@ function test_redis() {
tap_equal(2, $redis->rPush($key, 'C'), 'append C');
tap_equal(3, $redis->lPush($key, 'A'), 'prepend A');
- /* Redis->lGet is deprecated, but use it once to verify it works */
- tap_equal('A', @$redis->lGet($key, 0), 'retrieve element 0');
+ tap_equal('A', $redis->lIndex($key, 0), 'retrieve element 0');
tap_equal('B', $redis->lIndex($key, 1), 'retrieve element 1');
tap_equal('C', $redis->lIndex($key, 2), 'retrieve element 2');
tap_equal('C', $redis->lIndex($key, -1), 'retrieve last element');
@@ -199,8 +189,7 @@ function test_redis() {
tap_equal(4, $redis->rpush($key, 'A', 'B', 'B', 'C'), 'add new elements');
- /* Redis->lRemove is depreacted, but use it once to verity it works */
- tap_equal(1, @$redis->lRemove($key, 'B', 1), 'remove first occurence of B');
+ tap_equal(1, @$redis->lRem($key, 'B', 1), 'remove first occurence of B');
tap_equal(['A', 'B', 'C'], $redis->lrange($key, 0, -1), 'verify list elements');
tap_equal(0, $redis->lRem($key, 'NOT_IN_LIST', 1), 'remove missing element');
diff --git a/tests/integration/redis/test_list_logging_off.php b/tests/integration/redis/test_list_logging_off.php
index f693065da..e1a9aeb35 100644
--- a/tests/integration/redis/test_list_logging_off.php
+++ b/tests/integration/redis/test_list_logging_off.php
@@ -85,12 +85,9 @@
[{"name":"Datastore/operation/Redis/expire"}, [2, "??", "??", "??", "??", "??"]],
[{"name":"Datastore/operation/Redis/expire",
"scope":"OtherTransaction/php__FILE__"}, [2, "??", "??", "??", "??", "??"]],
- [{"name":"Datastore/operation/Redis/lget"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"Datastore/operation/Redis/lget",
- "scope":"OtherTransaction/php__FILE__"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"Datastore/operation/Redis/lindex"}, [8, "??", "??", "??", "??", "??"]],
+ [{"name":"Datastore/operation/Redis/lindex"}, [9, "??", "??", "??", "??", "??"]],
[{"name":"Datastore/operation/Redis/lindex",
- "scope":"OtherTransaction/php__FILE__"}, [8, "??", "??", "??", "??", "??"]],
+ "scope":"OtherTransaction/php__FILE__"}, [9, "??", "??", "??", "??", "??"]],
[{"name":"Datastore/operation/Redis/linsert"}, [2, "??", "??", "??", "??", "??"]],
[{"name":"Datastore/operation/Redis/linsert",
"scope":"OtherTransaction/php__FILE__"}, [2, "??", "??", "??", "??", "??"]],
@@ -109,12 +106,9 @@
[{"name":"Datastore/operation/Redis/lrange"}, [4, "??", "??", "??", "??", "??"]],
[{"name":"Datastore/operation/Redis/lrange",
"scope":"OtherTransaction/php__FILE__"}, [4, "??", "??", "??", "??", "??"]],
- [{"name":"Datastore/operation/Redis/lrem"}, [1, "??", "??", "??", "??", "??"]],
+ [{"name":"Datastore/operation/Redis/lrem"}, [2, "??", "??", "??", "??", "??"]],
[{"name":"Datastore/operation/Redis/lrem",
- "scope":"OtherTransaction/php__FILE__"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"Datastore/operation/Redis/lremove"}, [1, "??", "??", "??", "??", "??"]],
- [{"name":"Datastore/operation/Redis/lremove",
- "scope":"OtherTransaction/php__FILE__"}, [1, "??", "??", "??", "??", "??"]],
+ "scope":"OtherTransaction/php__FILE__"}, [2, "??", "??", "??", "??", "??"]],
[{"name":"Datastore/operation/Redis/lset"}, [1, "??", "??", "??", "??", "??"]],
[{"name":"Datastore/operation/Redis/lset",
"scope":"OtherTransaction/php__FILE__"}, [1, "??", "??", "??", "??", "??"]],
@@ -144,9 +138,6 @@
]
*/
-
-
-
require_once(realpath (dirname ( __FILE__ )) . '/../../include/helpers.php');
require_once(realpath (dirname ( __FILE__ )) . '/../../include/tap.php');
require_once(realpath (dirname ( __FILE__ )) . '/redis.inc');
@@ -172,8 +163,7 @@ function test_redis() {
tap_equal(2, $redis->rPush($key, 'C'), 'append C');
tap_equal(3, $redis->lPush($key, 'A'), 'prepend A');
- /* Redis->lGet is deprecated, but use it once to verify it works */
- tap_equal('A', @$redis->lGet($key, 0), 'retrieve element 0');
+ tap_equal('A', $redis->lIndex($key, 0), 'retrieve element 0');
tap_equal('B', $redis->lIndex($key, 1), 'retrieve element 1');
tap_equal('C', $redis->lIndex($key, 2), 'retrieve element 2');
tap_equal('C', $redis->lIndex($key, -1), 'retrieve last element');
@@ -202,8 +192,7 @@ function test_redis() {
tap_equal(4, $redis->rpush($key, 'A', 'B', 'B', 'C'), 'add new elements');
- /* Redis->lRemove is depreacted, but use it once to verity it works */
- tap_equal(1, @$redis->lRemove($key, 'B', 1), 'remove first occurence of B');
+ tap_equal(1, @$redis->lRem($key, 'B', 1), 'remove first occurence of B');
tap_equal(['A', 'B', 'C'], $redis->lrange($key, 0, -1), 'verify list elements');
tap_equal(0, $redis->lRem($key, 'NOT_IN_LIST', 1), 'remove missing element');
diff --git a/tests/integration/redis/test_lremove.php b/tests/integration/redis/test_lremove.php
new file mode 100644
index 000000000..49861c08b
--- /dev/null
+++ b/tests/integration/redis/test_lremove.php
@@ -0,0 +1,64 @@
+')) {
+ die("skip: Redis <= 5.3.7 required\n");
+}
+
+require("skipif.inc");
+*/
+
+/*INI
+newrelic.datastore_tracer.database_name_reporting.enabled = 1
+newrelic.datastore_tracer.instance_reporting.enabled = 1
+*/
+
+/*EXPECT
+ok - add new elements
+ok - remove first occurence of B
+ok - verify list elements
+*/
+
+/*EXPECT_METRICS_EXIST
+Datastore/operation/Redis/lremove
+*/
+
+require_once(realpath (dirname ( __FILE__ )) . '/../../include/helpers.php');
+require_once(realpath (dirname ( __FILE__ )) . '/../../include/integration.php');
+require_once(realpath (dirname ( __FILE__ )) . '/../../include/tap.php');
+require_once(realpath (dirname ( __FILE__ )) . '/redis.inc');
+
+function test_redis() {
+ global $REDIS_HOST, $REDIS_PORT;
+
+ $redis = new Redis();
+ $redis->connect($REDIS_HOST, $REDIS_PORT);
+
+ /* generate unique key names to use for this test run */
+ $key = randstr(16);
+ $key2 = "{$key}_b";
+ if ($redis->exists([$key, $key2])) {
+ die("skip: key(s) already exist: $key, $key2\n");
+ }
+
+ /* Ensure the keys don't persist (too much) longer than the test. */
+ $redis->expire($key, 30 /* seconds */);
+ $redis->expire($key2, 30 /* seconds */);
+
+ tap_equal(4, $redis->rpush($key, 'A', 'B', 'B', 'C'), 'add new elements');
+
+ /* Redis->lRemove is depreacted, but use it once to verity it works */
+ tap_equal(1, @$redis->lRemove($key, 'B', 1), 'remove first occurence of B');
+ tap_equal(['A', 'B', 'C'], $redis->lrange($key, 0, -1), 'verify list elements');
+}
+test_redis();