-
Notifications
You must be signed in to change notification settings - Fork 65
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
base: dev
Are you sure you want to change the base?
Conversation
ok jenkins |
2 similar comments
ok jenkins |
ok jenkins |
[{"name":"OtherTransactionTotalTime"}, [1, "??", "??", "??", "??", "??"]], | ||
[{"name":"OtherTransactionTotalTime/php__FILE__"}, [1, "??", "??", "??", "??", "??"]], | ||
[{"name":"Supportability/library/Guzzle 4-5/detected"}, | ||
[2, 0, 0, 0, 0, 0]], |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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]], |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this 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.
agent/lib_guzzle6.c
Outdated
} | ||
} | ||
|
||
void nr_guzzle7_minit(TSRMLS_D) { |
There was a problem hiding this comment.
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);
}
There was a problem hiding this comment.
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
agent/lib_guzzle6.c
Outdated
@@ -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) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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_guzzle_common.c
Outdated
@@ -272,19 +315,19 @@ char* nr_guzzle_response_get_header(const char* header, | |||
} | |||
|
|||
NR_PHP_WRAPPER_START(nr_guzzle_client_construct) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
agent/lib_guzzle6.c
Outdated
zval* config; | ||
zend_class_entry* guzzle_client_ce; | ||
zval* handler_stack; | ||
zval* middleware = NULL; | ||
zval* retval; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
@misiekn Thank you Michal for the detailed feedback! |
What are the chances of getting this merged in and available soon (ish)? |
There was a problem hiding this 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.
agent/lib_guzzle6.c
Outdated
zend_class_entry ce; | ||
char guzzle_path[] = "newrelic\\Guzzle6\\RequestHandler"; | ||
if (guzzle_version == 7){ | ||
nr_strcpy(guzzle_path, "newrelic\\Guzzle7\\RequestHandler"); |
There was a problem hiding this comment.
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);
...
There was a problem hiding this comment.
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
agent/lib_guzzle6.c
Outdated
* 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", |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
agent/lib_guzzle6.c
Outdated
@@ -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( |
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed! 40ab8cf
agent/lib_guzzle6.c
Outdated
NULL, "newrelic/Guzzle6" TSRMLS_CC); | ||
"}", guzzle_version, guzzle_version, guzzle_version); | ||
|
||
char *guzzle_ver = nr_formatf("newrelic/Guzzle%d", guzzle_version); |
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
agent/lib_guzzle_common.h
Outdated
* Returns : Non-zero if the object does implement the interface; zero | ||
* otherwise. | ||
* | ||
* Returns : A string indicating the guzzle version being used |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
agent/lib_guzzle6.c
Outdated
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 { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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){ |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed! 29f4551
/* | ||
* True global for the RequestHandler class entry. | ||
*/ | ||
zend_class_entry* nr_guzzle6_requesthandler_ce; | ||
int php_version_compare(char*, char*); |
There was a problem hiding this comment.
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
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 */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#endif /* 5.5.x */ | |
#endif /* 5.5.x */ | |
* Purpose : Performs tasks that we need performed on MINIT in the Guzzle 6 and 7 | ||
* instrumentation. |
There was a problem hiding this comment.
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
* 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 |
There was a problem hiding this comment.
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
There's a tar.bz2 file that has been committed... tests/include/guzzle.tar.bz2 |
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. |
There was a problem hiding this 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 👍
Is there something unique to guzzle in regards to testing that makes it so it requires this tar when the other frameworks don't? |
I did a search of the entire codebase for |
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. |
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
Maybe some kind of a README in the |
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. |
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. |
Hi, what's the status of this PR? It's affecting site performance for our platform. |
This PR implements the following changes: