Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

refactor(agent): cleanup guzzle instrumentation #498

Open
wants to merge 16 commits into
base: dev
Choose a base branch
from

Conversation

hahuja2
Copy link
Contributor

@hahuja2 hahuja2 commented Aug 1, 2022

This PR implements the following changes:

  • Combine Guzzle 6 instrumentation with Guzzle 7
  • Add guzzle version check function, which returns the correct guzzle version
  • Remove emitter function, which checks for emitter file in guzzle 4-5 package
  • Add Guzzle 7 test cases
  • Install Guzzle 7 dependencies and create new tar file with all guzzle packages included (Guzzle 4-7)
  • Guzzle 7 requires PHP 7.2 or above

@hahuja2
Copy link
Contributor Author

hahuja2 commented Aug 16, 2022

ok jenkins

2 similar comments
@zsistla
Copy link
Contributor

zsistla commented Aug 16, 2022

ok jenkins

@zsistla
Copy link
Contributor

zsistla commented Aug 16, 2022

ok jenkins

@newrelic newrelic deleted a comment from nr-opensource-bot Aug 16, 2022
agent/lib_guzzle_common.c Outdated Show resolved Hide resolved
[{"name":"OtherTransactionTotalTime"}, [1, "??", "??", "??", "??", "??"]],
[{"name":"OtherTransactionTotalTime/php__FILE__"}, [1, "??", "??", "??", "??", "??"]],
[{"name":"Supportability/library/Guzzle 4-5/detected"},
[2, 0, 0, 0, 0, 0]],
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this a 2?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for catching that! I need to look further into this and figure out why the program outputs a 2 for guzzle 4-5 detection. Guzzle 7 is being correctly detected, which is good since this is a guzzle 7 test case.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi Zach, I figured out that it was outputting a 2 for Guzzle 4-5 because the file path "clientinterface.php" was being detected twice inside of the guzzle 7 package. This is why I have updated the file path for each guzzle version to ensure that each file path is unique, so that we only detect the correct guzzle version based on which test is being run. 48f8e9e!

@@ -64,6 +64,7 @@
[{"name":"Supportability/library/Guzzle 4-5/detected"},
[1, 0, 0, 0, 0, 0]],
[{"name":"Supportability/library/Guzzle 6/detected"}, [1, 0, 0, 0, 0, 0]],
[{"name":"Supportability/library/Guzzle 7/detected"}, [1, 0, 0, 0, 0, 0]],
Copy link
Contributor

Choose a reason for hiding this comment

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

For the Guzzle 6 cases, is there some limitation such that we need to have Guzzle 7 detected?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In unpack_guzzle.php, this file extracts the different guzzle packages in the tar file (4-7) and imports the correct guzzle version. Then in php_execute.c, we define the nr_guzzle6_enable and nr_guzzle7_enable functions, which is the reason why guzzle 6 and 7 are both detected in the Guzzle 6 test cases. I will further look into this and see if I can prevent guzzle 7 from being detected

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed 48f8e9e! I have updated the file paths, which makes sure only one guzzle version is detected based on which guzzle test is being run

Copy link
Member

@lavarou lavarou left a comment

Choose a reason for hiding this comment

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

@hahuja2 Thank you for your work and contribution to make New Relic PHP Agent support the most recent version of Guzzle! 👍 I have a few suggestions that will improve future maintenance of New Relic PHP Agent by eliminating some of the code duplication and reducing complexity. Please have a look at my comments below.

tests/integration/external/guzzle7/test_dt.php Outdated Show resolved Hide resolved
agent/lib_guzzle6.h Outdated Show resolved Hide resolved
}
}

void nr_guzzle7_minit(TSRMLS_D) {
Copy link
Member

Choose a reason for hiding this comment

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

This function differs from nr_guzzle6_minit by a single character. I would strongly suggest extracting the common code into a helper function that would accept this difference as an input. Something in the form of:

static void nr_guzzleX_minit(const int guzzle_version) {
// paste the contents of `nr_guzzle6_minit` and use guzzle_version when constructing class entry
}

void nr_guzzle6_minit() {
  nr_guzzleX_minit(6);
}

void nr_guzzle7_minit() {
  nr_guzzleX_minit(7);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed, I have added a helper function called nr_guzzle_minit, which reduces code duplication. 566b26e

@@ -408,8 +404,8 @@ NR_PHP_WRAPPER_START(nr_guzzle6_client_construct) {
}
NR_PHP_WRAPPER_END

void nr_guzzle6_enable(TSRMLS_D) {
int retval;
void nr_guzzle7_enable(TSRMLS_D) {
Copy link
Member

Choose a reason for hiding this comment

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

This function differs from nr_guzzle6_enable only by a guzzle version number. I would strongly suggest extracting the common code into a helper function that would accept this difference as an input. Something in the form of:

static void nr_guzzleX_enable(const int guzzle_version) {
// paste the contents of `nr_guzzle7_enable` and use guzzle_version when constructing strings that utilize the version

}

void nr_guzzle6_enable() {
  nr_guzzleX_enable(6);
}

void nr_guzzle7_enable() {
  nr_guzzleX_enable(7);
}

Alternatively only leave original nr_guzzle6_enable but update all strings that reference guzzle version number to include two versions. Look at how it is done in lib_guzzle4.c for Guzzle 4 and Guzzle 5.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed! I have added a helper function called nr_guzzle_enable. I also have added a helper function called nr_guzzle_client_construct_helper, which helps reduce code duplication! 6222a51

agent/lib_guzzle4.c Show resolved Hide resolved
@@ -272,19 +315,19 @@ char* nr_guzzle_response_get_header(const char* header,
}

NR_PHP_WRAPPER_START(nr_guzzle_client_construct) {
Copy link
Member

Choose a reason for hiding this comment

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

Is this function still needed? If Guzzle library version is determined by a file name in nr_execute_handle_library, and correct nr_guzzleX_enable is used to enable instrumentation, then why version specific nr_guzzleX_enable calls the dispatch instrumentation (nr_guzzle_client_construct) instead of version specific instrumentation nr_guzzleX_client_construct?
I know that this function existed before this PR but maybe this PR could improve our codebase and eliminate unnecessary code complexity.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great point Michal! This is performing an unnecessary extra step because we have already identified the correct version by determining the correct file name in nr_execute_handle_library and can instead call the specific guzzle instrumentation in nr_guzzleX_enable

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have removed this function and now directly call the correct guzzle instrumentation inside of nr_guzzle_enable! 6a879ff

Comment on lines 72 to 76
zval* config;
zend_class_entry* guzzle_client_ce;
zval* handler_stack;
zval* middleware = NULL;
zval* retval;
Copy link
Member

Choose a reason for hiding this comment

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

Why do these variables need to be global?

Copy link
Contributor Author

@hahuja2 hahuja2 Aug 31, 2022

Choose a reason for hiding this comment

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

These variables were initially global because nr_guzzle6_client_construct and nr_guzzle7_client_construct both used these variables. However, I have added a helper function called nr_guzzle_client_construct_helper, which reduces code duplication and does not require these variables to be global anymore. Fixed! 6222a51

@hahuja2
Copy link
Contributor Author

hahuja2 commented Aug 29, 2022

@misiekn Thank you Michal for the detailed feedback!

@RuthAdele
Copy link

What are the chances of getting this merged in and available soon (ish)?

Copy link
Member

@lavarou lavarou left a comment

Choose a reason for hiding this comment

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

@hahuja2 Thank you for refactoring the code to reduce code duplication! 👍 After looking at the code again I come to the conclusion that including Guzzle version in the namespace (newrelic\Guzzle6 or newrelic\Guzzle7) adds unnecessary code complexity and introduces a few memory leaks. I do not think the Guzzle version is necessary in the namespace name. Please have a look at my comments below which have more details.

zend_class_entry ce;
char guzzle_path[] = "newrelic\\Guzzle6\\RequestHandler";
if (guzzle_version == 7){
nr_strcpy(guzzle_path, "newrelic\\Guzzle7\\RequestHandler");
Copy link
Member

Choose a reason for hiding this comment

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

This doesn't seem the right approach. Luckily declaration (and initialization) on line 348 creates char array of exactly the size enough to fit this strcpy. Maybe consider turning guzzle_path into a function that will return const char * based on guzzle_version? Something like this:

static const char *
make_request_handler_class_name(const int guzzle_version) {
    if (6 == guzzle_version) {
        return "newrelic\\Guzzle6\\RequestHandler";
    }
    if (7 == guzzle_version) {
        return "newrelic\\Guzzle7\\RequestHandler";
    }
    return NULL;
}
...
static void nr_guzzle_minit(const int guzzle_version){
...
    const char *nr_guzzle_request_handler_class_name = NULL;
...
    if (0 == NRINI(guzzle_enabled)) {
        return;
    }
    nr_guzzle_request_handler_class_name = make_request_handler_class_name(guzzle_version);
    if (NULL == nr_guzzle_request_handler_class_name) {
        return;
    }
    INIT_CLASS_ENTRY(ce, nr_guzzle_request_handler_class_name, nr_guzzle6_requesthandler_functions);
...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I also agree that using nr_strcpy is not the most efficient way. I have fixed this by removing the guzzle version from the namespace! 40ab8cf

* Get our middleware callable (which is just a string), and make sure it's
* actually callable before we invoke push(). (See also PHP-1184.)
*/
char *str_middleware = nr_formatf("newrelic\\Guzzle%d\\middleware",
Copy link
Member

Choose a reason for hiding this comment

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

I don't see this being freed.

Copy link
Contributor Author

@hahuja2 hahuja2 Sep 21, 2022

Choose a reason for hiding this comment

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

Thank you for pointing that out! Since guzzle version is not needed in the namespace, I will not have to use nr_formatf() anymore. 40ab8cf

@@ -429,20 +379,20 @@ void nr_guzzle6_enable(TSRMLS_D) {
* as a standalone file, so we can use a normal namespace declaration to
* avoid possible clashes.
*/
retval = zend_eval_string(
"namespace newrelic\\Guzzle6;"
char *eval = nr_formatf(
Copy link
Member

Choose a reason for hiding this comment

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

I don't see eval being freed...

Copy link
Contributor Author

@hahuja2 hahuja2 Sep 21, 2022

Choose a reason for hiding this comment

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

Fixed! 40ab8cf

NULL, "newrelic/Guzzle6" TSRMLS_CC);
"}", guzzle_version, guzzle_version, guzzle_version);

char *guzzle_ver = nr_formatf("newrelic/Guzzle%d", guzzle_version);
Copy link
Member

Choose a reason for hiding this comment

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

I don't see guzzle_ver being freed...

Copy link
Contributor Author

@hahuja2 hahuja2 Sep 21, 2022

Choose a reason for hiding this comment

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

Fixed 40ab8cf

zval* this_var = nr_php_scope_get(NR_EXECUTE_ORIG_ARGS TSRMLS_CC);
zval* retval;
int guzzle_version = 6;
char *version = nr_guzzle_version(this_var);
Copy link
Member

Choose a reason for hiding this comment

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

Does version need to be freed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, version also needs to be freed because the nr_guzzle_version function allocates memory for a new string and does not free it, which means the caller is responsible for freeing the result. 29f4551

* Returns : Non-zero if the object does implement the interface; zero
* otherwise.
*
* Returns : A string indicating the guzzle version being used
Copy link
Member

Choose a reason for hiding this comment

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

Does this function allocate a new string? Is the caller responsible for freeing the result?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, it allocates a new string and the caller is responsible for freeing the returned result. I have updated the description of the function to include that the returned string must be freed. 8065805

Comment on lines 416 to 422
if (SUCCESS == _retval && guzzle_version == 6) {
nr_php_wrap_user_function(NR_PSTR("GuzzleHttp\\Client::__construct"),
nr_guzzle_client_construct TSRMLS_CC);
} else {
nr_guzzle6_client_construct TSRMLS_CC);
}else if (SUCCESS == _retval && guzzle_version == 7){
nr_php_wrap_user_function(NR_PSTR("GuzzleHttp\\Client::__construct"),
nr_guzzle7_client_construct TSRMLS_CC);
}else {
Copy link
Member

Choose a reason for hiding this comment

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

I think this code can be simplified further because nr_guzzle6_client_construct and nr_guzzle7_client_construct are actually nr_guzzle_client_construct_helper. Also I don't think the guzzle_version needs to be used in any of the New Relic instrumenting namespaces or class names. This would simplify the code and reduce memory allocations.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, that is a great point! I have simplified the code 40ab8cf

nrl_warning(NRL_FRAMEWORK,
"%s: error evaluating PHP code; not installing handler",
__func__);
}
}

void nr_guzzle6_minit(TSRMLS_D) {
zend_class_entry ce;
NR_PHP_WRAPPER_START(nr_guzzle_client_construct_helper){
Copy link
Member

Choose a reason for hiding this comment

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

Maybe add a brief doc comment describing what this function does? I.e. it adds newrelic\Guzzle\middleware to the GuzzleHttp\Client handler stack.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea! I have added a brief description that describes what this function is doing f3ac653

"%s: middleware string is not considered callable",
__func__);

char* error_message = nr_formatf(
Copy link
Member

Choose a reason for hiding this comment

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

I don't see error_message being freed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed! 29f4551

@mfulb mfulb added this to the Upcoming Release milestone Sep 21, 2022
/*
* True global for the RequestHandler class entry.
*/
zend_class_entry* nr_guzzle6_requesthandler_ce;
int php_version_compare(char*, char*);
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems unnecessary. Instead, #include "ext/standard/php_versioning.h" should be added to the #include statements

Suggested change
int php_version_compare(char*, char*);

@@ -504,4 +522,4 @@ void nr_guzzle6_minit(TSRMLS_D) {
NR_UNUSED_TSRMLS;
}

#endif /* 5.5.x */
#endif /* 5.5.x */
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
#endif /* 5.5.x */
#endif /* 5.5.x */

Comment on lines +15 to 16
* Purpose : Performs tasks that we need performed on MINIT in the Guzzle 6 and 7
* instrumentation.
Copy link
Contributor

Choose a reason for hiding this comment

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

Needs to be clang formatted

Suggested change
* Purpose : Performs tasks that we need performed on MINIT in the Guzzle 6 and 7
* instrumentation.
* Purpose : Performs tasks that we need performed on MINIT in the Guzzle 6 and
* 7 instrumentation.

@@ -9,14 +9,14 @@
* It is a required component in Drupal 8, and strongly recommended by other
Copy link
Contributor

Choose a reason for hiding this comment

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

This file needs to be clang formatted

@bduranleau-nr
Copy link
Contributor

There's a tar.bz2 file that has been committed... tests/include/guzzle.tar.bz2
Was this intentional?
There are no other tar files in the repository, and I don't think we should depart from that format here. I would suggest removing guzzle.tar.bz2 from the repository

@ZNeumann
Copy link
Contributor

There's a tar.bz2 file that has been committed... tests/include/guzzle.tar.bz2 Was this intentional? There are no other tar files in the repository, and I don't think we should depart from that format here. I would suggest removing guzzle.tar.bz2 from the repository

There are other tar files in that directory as well. It's where we put the libraries to run for tests, so we have guzzle and monolog in there. It needs to be changed because this PR is adding guzzle 7 to it.

Copy link
Contributor

@bduranleau-nr bduranleau-nr left a comment

Choose a reason for hiding this comment

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

Great job!
Made a few suggestions for changes, nothing major, other than that looks good to go 👍

@bduranleau-nr
Copy link
Contributor

There are other tar files in that directory as well. It's where we put the libraries to run for tests, so we have guzzle and monolog in there. It needs to be changed because this PR is adding guzzle 7 to it.

Is there something unique to guzzle in regards to testing that makes it so it requires this tar when the other frameworks don't?

@bduranleau-nr
Copy link
Contributor

There are other tar files in that directory as well.

I did a search of the entire codebase for *.tar* and this tar file was the only one that came up.

@ZNeumann
Copy link
Contributor

There are other tar files in that directory as well. It's where we put the libraries to run for tests, so we have guzzle and monolog in there. It needs to be changed because this PR is adding guzzle 7 to it.

Is there something unique to guzzle in regards to testing that makes it so it requires this tar when the other frameworks don't?

Added here b5c11b9 for monolog, and long before that for guzzle.

I don't know why it was decided to do it this way, but it seems like libraries (as opposed to frameworks) are added via tars in this directory.

@bduranleau-nr
Copy link
Contributor

Added here b5c11b9 for monolog, and long before that for guzzle.

I see- I was searching the checkout of this PR, so it was missing (a lot) of the more recent changes, including the monolog2/3 tars

I don't know why it was decided to do it this way, but it seems like libraries (as opposed to frameworks) are added via tars in this directory.

Maybe some kind of a README in the tests/ or tests/include/ directory with an explanation of what the tar file is and why it is implemented this way would be a good addition?

@lavarou lavarou removed this from the Upcoming Release milestone Oct 26, 2022
@lavarou
Copy link
Member

lavarou commented Aug 16, 2023

Guzzle integration tests have been enhanced in #713. That enhancement contains integration tests from this PR. No other changes from this PR have been moved over.

@joshua-bn
Copy link

This PR has been open for 13 months at this point. Is there something missing that needs to be added?

@lavarou
Copy link
Member

lavarou commented Aug 30, 2023

This PR has been open for 13 months at this point. Is there something missing that needs to be added?

@joshua-bn @RuthAdele Thank you for your interest in New Relic PHP Agent. While this PR refactors the code used to instrument Guzzle, New Relic PHP Agent supports Guzzle 7 - #713 added tests to verify Guzzle 7 instrumentation. New Relic PHP Agent documentation has also been recently updated to reflect that Guzzle 7 is supported - see here.

@lavarou lavarou changed the title feat(agent): Add Guzzle 7 Library Support refactor(agent): cleanup guzzle instrumentation Aug 30, 2023
@lavarou lavarou added on hold This issue or pull request is necessary, but better suited for the future and removed ready for review labels Dec 6, 2023
@scottbuscemi
Copy link

Hi, what's the status of this PR? It's affecting site performance for our platform.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
on hold This issue or pull request is necessary, but better suited for the future
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants