diff --git a/agent/lib_composer.c b/agent/lib_composer.c index 674bff3c3..eeb363b57 100644 --- a/agent/lib_composer.c +++ b/agent/lib_composer.c @@ -162,6 +162,23 @@ static void nr_execute_handle_autoload_composer_get_packages_information( } } ZEND_HASH_FOREACH_END(); + + /* cache Composer packages if only running once per process */ + if (NR_PHP_PROCESS_GLOBALS(composer_api_per_process_detection)) { + nr_php_packages_destroy(&NR_PHP_PROCESS_GLOBALS(composer_php_packages)); + NR_PHP_PROCESS_GLOBALS(composer_php_packages) + = nr_php_packages_clone(NRPRG(txn)->php_packages); + if (NULL == NR_PHP_PROCESS_GLOBALS(composer_php_packages)) { + nrl_verbosedebug(NRL_INSTRUMENT, + "%s - unable to clone composer packages", __func__); + } else { + nrl_verbosedebug(NRL_INSTRUMENT, "%s - cloned %zu composer packages", + __func__, + nr_php_packages_count( + NR_PHP_PROCESS_GLOBALS(composer_php_packages))); + } + } + } else { char strbuf[80]; nr_format_zval_for_debug(&retval, strbuf, 0, sizeof(strbuf) - 1, 0); diff --git a/agent/php_globals.c b/agent/php_globals.c index 5bb372d33..d5cfe7ab8 100644 --- a/agent/php_globals.c +++ b/agent/php_globals.c @@ -42,6 +42,7 @@ static void nr_php_per_process_globals_dispose(void) { nr_free(nr_php_per_process_globals.env_labels); nr_free(nr_php_per_process_globals.apache_add); nr_free(nr_php_per_process_globals.docker_id); + nr_php_packages_destroy(&nr_php_per_process_globals.composer_php_packages); nr_memset(&nr_php_per_process_globals, 0, sizeof(nr_php_per_process_globals)); } diff --git a/agent/php_globals.h b/agent/php_globals.h index 4d0a2fe24..8ebecc8de 100644 --- a/agent/php_globals.h +++ b/agent/php_globals.h @@ -82,6 +82,8 @@ typedef struct _nrphpglobals_t { detection has run. Used in conjunction with composer_api_per_process_detection. */ char* docker_id; /* 64 byte hex docker ID parsed from /proc/self/mountinfo */ + nr_php_packages_t* composer_php_packages; /* Cache of PHP packages detected in + the current process. */ /* Original PHP callback pointer contents */ nrphperrfn_t orig_error_cb; diff --git a/agent/php_txn.c b/agent/php_txn.c index 4b92f40c6..4b4fb3bd6 100644 --- a/agent/php_txn.c +++ b/agent/php_txn.c @@ -1151,6 +1151,24 @@ nr_status_t nr_php_txn_begin(const char* appnames, } } + /* preload cached composer packages if present */ + if (NR_PHP_PROCESS_GLOBALS(composer_packages_detected) + && (NULL != NR_PHP_PROCESS_GLOBALS(composer_php_packages))) { + nrl_verbosedebug(NRL_FRAMEWORK, + "composer packages already detected, using cached values"); +#if 0 + nr_php_packages_destroy(&NRPRG(txn)->php_packages); + NRPRG(txn)->php_packages + = nr_php_packages_clone(NR_PHP_PROCESS_GLOBALS(composer_php_packages)); + nrl_verbosedebug(NRL_FRAMEWORK, "composer packages cloned from cache"); +#else + nrl_verbosedebug(NRL_INSTRUMENT, "passing known PHP packages = %p", + NR_PHP_PROCESS_GLOBALS(composer_php_packages)); + nr_php_packages_set_known(NRPRG(txn)->php_packages, + NR_PHP_PROCESS_GLOBALS(composer_php_packages)); +#endif + } + return NR_SUCCESS; } diff --git a/axiom/nr_php_packages.c b/axiom/nr_php_packages.c index b8a034086..ea7df9fa5 100644 --- a/axiom/nr_php_packages.c +++ b/axiom/nr_php_packages.c @@ -36,6 +36,17 @@ static inline const char* nr_php_package_source_priority_to_string(const nr_php_ } } +void nr_php_packages_set_known(nr_php_packages_t* pkgs, + nr_php_packages_t* known) { /* should be CONST */ + if (NULL == pkgs || NULL == known || NULL == known->data) { + return; + } + + nrl_verbosedebug(NRL_INSTRUMENT, "Setting known PHP packages to %p", known); + + pkgs->known_packages = known->data; +} + nr_php_package_t* nr_php_package_create_with_source( const char* name, const char* version, @@ -90,9 +101,91 @@ nr_php_packages_t* nr_php_packages_create() { nr_free(h); return NULL; } + + h->known_packages = NULL; + + return h; +} +static void clone_callback(void* value, + const char* key, + size_t key_len, + void* userdata) { + nr_php_packages_t* dest = NULL; + nr_php_package_t* orig_pkg = NULL; + nr_php_package_t* new_pkg = NULL; + + (void)key_len; // Unused parameter + (void)key; // Unused parameter + + if (NULL == value || NULL == userdata) { + return; + } + + if (NULL == ((nr_php_packages_t*)userdata)->data) { + return; + } + + /* Clone the package and add it to the destination hashmap */ + // userdata is a pointer to nr_php_packages_t + // value is a pointer to nr_php_package_t + // we will clone the package and add it to the destination hashmap + dest = (nr_php_packages_t*)userdata; + orig_pkg = (nr_php_package_t*)value; + if (NULL == orig_pkg) { + return; + } + new_pkg = nr_php_package_create_with_source(orig_pkg->package_name, + orig_pkg->package_version, + orig_pkg->source_priority); + nr_php_packages_add_package(dest, new_pkg); +} + +nr_php_packages_t* nr_php_packages_clone(nr_php_packages_t* pkgs) { + nr_php_packages_t* h = NULL; + + if (NULL == pkgs) { + return NULL; + } + + h = nr_php_packages_create(); + if (NULL == h) { + return NULL; + } + + nr_hashmap_apply(pkgs->data, clone_callback, h); + return h; } +static inline bool nr_php_packages_is_known_package(nr_php_packages_t* pkgs, + nr_php_package_t* p) { + nr_php_package_t* known = NULL; + + if (NULL == pkgs || NULL == pkgs->known_packages || NULL == p) { + return false; + } + + known = (nr_php_package_t*)nr_hashmap_get( + pkgs->known_packages, p->package_name, nr_strlen(p->package_name)); + + if (NULL == known) { + // do not ignore - package is not known yet + return false; + } + + if (known->source_priority > p->source_priority) { + // ignore - package is known and has a lower priority + return true; + } + + if (0 == nr_strcmp(known->package_version, p->package_version)) { + // ignore - package is known and has the same version + return true; + } + + return false; +} + nr_php_package_t* nr_php_packages_add_package(nr_php_packages_t* h, nr_php_package_t* p) { nr_php_package_t* package; @@ -104,6 +197,12 @@ nr_php_package_t* nr_php_packages_add_package(nr_php_packages_t* h, return NULL; } + // if package is in known packages then ignore + if (nr_php_packages_is_known_package(h, p)) { + nr_php_package_destroy(p); + return NULL; + } + // If package with the same key already exists, we will check if the value is // different. If it is different, then we will update the value of the package package = (nr_php_package_t*)nr_hashmap_get(h->data, p->package_name, diff --git a/axiom/nr_php_packages.h b/axiom/nr_php_packages.h index f4945c43e..48b81d7ad 100644 --- a/axiom/nr_php_packages.h +++ b/axiom/nr_php_packages.h @@ -28,6 +28,8 @@ typedef struct _nr_php_package_t { typedef struct _nr_php_packages_t { nr_hashmap_t* data; + nr_hashmap_t* known_packages; + /* SHOULD BE CONST !!!*/ } nr_php_packages_t; typedef void(nr_php_packages_iter_t)(void* value, @@ -35,6 +37,9 @@ typedef void(nr_php_packages_iter_t)(void* value, size_t name_len, void* user_data); +void nr_php_packages_set_known(nr_php_packages_t* pkgs, + nr_php_packages_t* known); /* should be CONST! */ + /* * Purpose : Create a new php package with desired source priority. If the name is null, then no package will * be created. If the version is null (version = NULL), then @@ -70,6 +75,14 @@ extern nr_php_package_t* nr_php_package_create_with_source( extern nr_php_package_t* nr_php_package_create(const char* name, const char* version); +/* Purpose : Clone a collection of php packages + * + * Params : 1. A pointer to nr_php_packages_t + * + * Returns : A new nr_php_packages_t that is a copy of the original + */ +nr_php_packages_t* nr_php_packages_clone(nr_php_packages_t* pkgs); + /* * Purpose : Destroy/free php package * diff --git a/axiom/tests/test_php_packages.c b/axiom/tests/test_php_packages.c index bd2323b2f..b7954daa9 100644 --- a/axiom/tests/test_php_packages.c +++ b/axiom/tests/test_php_packages.c @@ -53,6 +53,75 @@ static void test_php_adding_packages_to_hashmap(void) { tlib_pass_if_null("PHP packages hashmap destroyed", hm); } +static void test_adding_to_hashmap_with_known_packages(void) { + nr_php_package_t* package1; + nr_php_package_t* package2; + nr_php_package_t* package3; + nr_php_packages_t* hm = nr_php_packages_create(); + nr_php_packages_t* cached_packages; + int count; + + // Test: create multiple new packages and add to hashmap + package1 = nr_php_package_create("Package One", "10.1.0"); + package2 = nr_php_package_create("Package Two", "11.2.0"); + package3 = nr_php_package_create("Package Three", "12.3.0"); + /* Should not crash: */ + + nr_php_packages_add_package(NULL, package1); + nr_php_packages_add_package(hm, NULL); + nr_php_packages_add_package(hm, package1); + nr_php_packages_add_package(hm, package2); + nr_php_packages_add_package(hm, package3); + + count = nr_php_packages_count(hm); + + tlib_pass_if_int_equal("package count", 3, count); + + // Cache packages + cached_packages = nr_php_packages_clone(hm); + + nr_php_packages_destroy(&hm); + tlib_pass_if_null("Transaction packages destroyed", hm); + + tlib_pass_if_not_null("Packages cache created", cached_packages); + tlib_pass_if_int_equal("Packages cache count", 3, + nr_php_packages_count(cached_packages)); + + hm = nr_php_packages_create(); + tlib_pass_if_not_null("Transaction packages created", hm); + + nr_php_packages_set_known(hm, cached_packages); + tlib_pass_if_ptr_equal("Known packages set", + hm->known_packages, cached_packages->data); + + // Adding packages to hashmap that are already in the cache should not change + // the count of packages in the hashmap + package1 = nr_php_package_create("Package One", "10.1.0"); + package2 = nr_php_package_create("Package Two", "11.2.0"); + package3 = nr_php_package_create("Package Three", "12.3.0"); + nr_php_packages_add_package(hm, package1); + nr_php_packages_add_package(hm, package2); + nr_php_packages_add_package(hm, package3); + + count = nr_php_packages_count(hm); + + tlib_pass_if_int_equal("package count", 0, count); + + // Adding packages to hashmap that are not in the cache should change + // the count of packages in the hashmap + package1 = nr_php_package_create("Uncached package", "12.3.0"); + nr_php_packages_add_package(hm, package1); + count = nr_php_packages_count(hm); + + tlib_pass_if_int_equal("package count", 1, count); + + nr_php_packages_destroy(&hm); + tlib_pass_if_null("Transaction packages destroyed", hm); + + nr_php_packages_destroy(&cached_packages); + tlib_pass_if_null("Cached packages hashmap destroyed", cached_packages); +} + static void test_php_package_to_json(void) { char* json; nr_php_package_t* package1; @@ -375,6 +444,7 @@ tlib_parallel_info_t parallel_info void test_main(void* p NRUNUSED) { test_php_package_create_destroy(); test_php_adding_packages_to_hashmap(); + test_adding_to_hashmap_with_known_packages(); test_php_package_to_json(); test_php_packages_to_json_buffer(); test_php_packages_to_json();