Skip to content
Closed
17 changes: 17 additions & 0 deletions agent/lib_composer.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions agent/php_globals.c
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
Expand Down
2 changes: 2 additions & 0 deletions agent/php_globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
18 changes: 18 additions & 0 deletions agent/php_txn.c
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code needs some wrapping conditional so it isn't used if agent is running Composer API every request.

Probably wrapping it with:

if (NR_PHP_PROCESS_GLOBALS(composer_api_per_process_detection)) {
...
}

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;
}

Expand Down
99 changes: 99 additions & 0 deletions axiom/nr_php_packages.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand All @@ -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,
Expand Down
13 changes: 13 additions & 0 deletions axiom/nr_php_packages.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,18 @@ 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,
const char* name,
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
Expand Down Expand Up @@ -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
*
Expand Down
70 changes: 70 additions & 0 deletions axiom/tests/test_php_packages.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down