Skip to content

Commit 8ce07e7

Browse files
committed
feat(agent): combine Guzzle 6 instrumentation with Guzzle 7
1 parent ed6b655 commit 8ce07e7

File tree

8 files changed

+170
-42
lines changed

8 files changed

+170
-42
lines changed

agent/fw_hooks.h

+1
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ extern void nr_doctrine2_enable(TSRMLS_D);
4848
extern void nr_guzzle3_enable(TSRMLS_D);
4949
extern void nr_guzzle4_enable(TSRMLS_D);
5050
extern void nr_guzzle6_enable(TSRMLS_D);
51+
extern void nr_guzzle7_enable(TSRMLS_D);
5152
extern void nr_laminas_http_enable(TSRMLS_D);
5253
extern void nr_mongodb_enable(TSRMLS_D);
5354
extern void nr_phpunit_enable(TSRMLS_D);

agent/lib_guzzle6.c

+158-33
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@
99
* It is a required component in Drupal 8, and strongly recommended by other
1010
* frameworks, including Symfony 2 and 3.
1111
*
12-
* Our approach for Guzzle 6 is to register middleware on every client that
12+
* Our approach for Guzzle 6-7 is to register middleware on every client that
1313
* adds our headers to the request object, handles responses, and creates
1414
* metrics and trace nodes using the internal RequestHandler class declared
1515
* below.
1616
*
1717
* There is one issue with this approach, which is that the middleware is
1818
* called when the request is created, rather than when the request is sent. As
19-
* Guzzle 6 removed the event system that allowed us to know exactly when the
19+
* Guzzle 6-7 removed the event system that allowed us to know exactly when the
2020
* request was sent, we are unable to get the time of the request being sent
2121
* without instrumenting much more deeply into Guzzle's handlers. We consider
2222
* this to be an obscure enough edge case that we are not doing this work at
@@ -65,12 +65,15 @@
6565
*/
6666
#if ZEND_MODULE_API_NO >= ZEND_5_5_X_API_NO
6767

68-
/* {{{ newrelic\Guzzle6\RequestHandler class definition and methods */
69-
7068
/*
7169
* True global for the RequestHandler class entry.
7270
*/
7371
zend_class_entry* nr_guzzle6_requesthandler_ce;
72+
zval* config;
73+
zend_class_entry* guzzle_client_ce;
74+
zval* handler_stack;
75+
zval* middleware = NULL;
76+
zval* retval;
7477

7578
/*
7679
* Arginfo for the RequestHandler methods.
@@ -107,7 +110,7 @@ static void nr_guzzle6_requesthandler_handle_response(zval* handler,
107110
zval* response
108111
TSRMLS_DC) {
109112
nr_segment_t* segment = NULL;
110-
nr_segment_external_params_t external_params = {.library = "Guzzle 6"};
113+
nr_segment_external_params_t external_params = {.library = "Guzzle 6-7"};
111114
zval* request;
112115
zval* method;
113116
zval* status;
@@ -140,7 +143,7 @@ static void nr_guzzle6_requesthandler_handle_response(zval* handler,
140143

141144
if (NRPRG(txn) && NRTXN(special_flags.debug_cat)) {
142145
nrl_verbosedebug(
143-
NRL_CAT, "CAT: outbound response: transport='Guzzle 6' %s=" NRP_FMT,
146+
NRL_CAT, "CAT: outbound response: transport='Guzzle 6-7' %s=" NRP_FMT,
144147
X_NEWRELIC_APP_DATA, NRP_CAT(external_params.encoded_response_header));
145148
}
146149

@@ -206,14 +209,14 @@ static PHP_NAMED_FUNCTION(nr_guzzle6_requesthandler_construct) {
206209
zend_update_property(Z_OBJCE_P(this_obj), ZVAL_OR_ZEND_OBJECT(this_obj),
207210
NR_PSTR("request"), request TSRMLS_CC);
208211

209-
nr_guzzle_obj_add(this_obj, "Guzzle 6" TSRMLS_CC);
212+
nr_guzzle_obj_add(this_obj, "Guzzle 6-7" TSRMLS_CC);
210213
}
211214

212215
/*
213216
* Proto : void RequestHandler::onFulfilled(Psr\Http\Message\ResponseInterface
214217
* $response)
215218
*
216-
* Purpose : Called when a Guzzle 6 request promise is fulfilled.
219+
* Purpose : Called when a Guzzle 6-7 request promise is fulfilled.
217220
*
218221
* Params : 1. The response object.
219222
*/
@@ -257,7 +260,7 @@ static PHP_NAMED_FUNCTION(nr_guzzle6_requesthandler_onfulfilled) {
257260
* Proto : void
258261
* RequestHandler::onRejected(GuzzleHttp\Exception\TransferException $e)
259262
*
260-
* Purpose : Called when a Guzzle 6 request promise failed.
263+
* Purpose : Called when a Guzzle 6-7 request promise failed.
261264
*
262265
* Params : 1. The exception object.
263266
*/
@@ -340,40 +343,33 @@ const zend_function_entry nr_guzzle6_requesthandler_functions[]
340343
nr_guzzle6_requesthandler_onrejected_arginfo,
341344
ZEND_ACC_PUBLIC) PHP_FE_END};
342345

343-
/* }}} */
346+
/*
347+
* Guzzle 7 requires PHP 7.2.0 or later, which is why we will not build Guzzle 7
348+
* support on older versions and will instead provide simple stubs for the two
349+
* exported functions to avoid linking errors.
350+
*/
351+
#if ZEND_MODULE_API_NO >= ZEND_7_2_X_API_NO
344352

345-
NR_PHP_WRAPPER_START(nr_guzzle6_client_construct) {
346-
zval* config;
347-
zend_class_entry* guzzle_client_ce;
348-
zval* handler_stack;
349-
zval* middleware = NULL;
350-
zval* retval;
353+
NR_PHP_WRAPPER_START(nr_guzzle7_client_construct){
351354
zval* this_var = nr_php_scope_get(NR_EXECUTE_ORIG_ARGS TSRMLS_CC);
352355

353356
(void)wraprec;
354357
NR_UNUSED_SPECIALFN;
355-
356-
/* This is how we distinguish Guzzle 4/5. */
357-
if (nr_guzzle_does_zval_implement_has_emitter(this_var TSRMLS_CC)) {
358-
NR_PHP_WRAPPER_CALL;
359-
goto end;
360-
}
361-
362358
NR_PHP_WRAPPER_CALL;
363359

364360
/*
365361
* Get our middleware callable (which is just a string), and make sure it's
366362
* actually callable before we invoke push(). (See also PHP-1184.)
367363
*/
368364
middleware = nr_php_zval_alloc();
369-
nr_php_zval_str(middleware, "newrelic\\Guzzle6\\middleware");
365+
nr_php_zval_str(middleware, "newrelic\\Guzzle7\\middleware");
370366
if (!nr_php_is_zval_valid_callable(middleware TSRMLS_CC)) {
371367
nrl_verbosedebug(NRL_FRAMEWORK,
372368
"%s: middleware string is not considered callable",
373369
__func__);
374370

375371
nrm_force_add(NRTXN(unscoped_metrics),
376-
"Supportability/library/Guzzle 6/MiddlewareNotCallable", 0);
372+
"Supportability/library/Guzzle 7/MiddlewareNotCallable", 0);
377373

378374
goto end;
379375
}
@@ -408,8 +404,8 @@ NR_PHP_WRAPPER_START(nr_guzzle6_client_construct) {
408404
}
409405
NR_PHP_WRAPPER_END
410406

411-
void nr_guzzle6_enable(TSRMLS_D) {
412-
int retval;
407+
void nr_guzzle7_enable(TSRMLS_D) {
408+
int _retval;
413409

414410
if (0 == NRINI(guzzle_enabled)) {
415411
return;
@@ -429,20 +425,20 @@ void nr_guzzle6_enable(TSRMLS_D) {
429425
* as a standalone file, so we can use a normal namespace declaration to
430426
* avoid possible clashes.
431427
*/
432-
retval = zend_eval_string(
433-
"namespace newrelic\\Guzzle6;"
428+
_retval = zend_eval_string(
429+
"namespace newrelic\\Guzzle7;"
434430

435431
"use Psr\\Http\\Message\\RequestInterface;"
436432

437-
"if (!function_exists('newrelic\\Guzzle6\\middleware')) {"
433+
"if (!function_exists('newrelic\\Guzzle7\\middleware')) {"
438434
" function middleware(callable $handler) {"
439435
" return function (RequestInterface $request, array $options) use "
440436
"($handler) {"
441437

442438
/*
443439
* Start by adding the outbound CAT/DT/Synthetics headers to the request.
444440
*/
445-
" foreach (newrelic_get_request_metadata('Guzzle 6') as $k => $v) {"
441+
" foreach (newrelic_get_request_metadata('Guzzle 7') as $k => $v) {"
446442
" $request = $request->withHeader($k, $v);"
447443
" }"
448444

@@ -455,13 +451,142 @@ void nr_guzzle6_enable(TSRMLS_D) {
455451
" $promise = $handler($request, $options);"
456452
" $promise->then([$rh, 'onFulfilled'], [$rh, 'onRejected']);"
457453

454+
" return $promise;"
455+
" };"
456+
" }"
457+
"}",
458+
NULL, "newrelic/Guzzle7" TSRMLS_CC);
459+
460+
if (SUCCESS == _retval) {
461+
nr_php_wrap_user_function(NR_PSTR("GuzzleHttp\\Client::__construct"),
462+
nr_guzzle_client_construct TSRMLS_CC);
463+
} else {
464+
nrl_warning(NRL_FRAMEWORK,
465+
"%s: error evaluating PHP code; not installing handler",
466+
__func__);
467+
}
468+
}
469+
470+
void nr_guzzle7_minit(TSRMLS_D) {
471+
zend_class_entry ce;
472+
473+
if (0 == NRINI(guzzle_enabled)) {
474+
return;
475+
}
476+
477+
INIT_CLASS_ENTRY(ce, "newrelic\\Guzzle7\\RequestHandler",
478+
nr_guzzle6_requesthandler_functions);
479+
nr_guzzle6_requesthandler_ce
480+
= nr_php_zend_register_internal_class_ex(&ce, NULL TSRMLS_CC);
481+
482+
zend_declare_property_null(nr_guzzle6_requesthandler_ce, NR_PSTR("request"),
483+
ZEND_ACC_PRIVATE TSRMLS_CC);
484+
}
485+
486+
487+
#else /* PHP < 7.2 */
488+
489+
NR_PHP_WRAPPER_START(nr_guzzle7_client_construct) {
490+
(void)wraprec;
491+
NR_UNUSED_SPECIALFN;
492+
NR_UNUSED_TSRMLS;
493+
}
494+
NR_PHP_WRAPPER_END
495+
496+
void nr_guzzle7_enable(TSRMLS_D) {
497+
NR_UNUSED_TSRMLS
498+
}
499+
500+
void nr_guzzle7_minit(TSRMLS_D) {
501+
NR_UNUSED_TSRMLS;
502+
}
503+
504+
#endif /* 7.2.x */
505+
506+
NR_PHP_WRAPPER_START(nr_guzzle6_client_construct) {
507+
zval* this_var = nr_php_scope_get(NR_EXECUTE_ORIG_ARGS TSRMLS_CC);
508+
509+
(void)wraprec;
510+
NR_UNUSED_SPECIALFN;
511+
512+
NR_PHP_WRAPPER_CALL;
513+
middleware = nr_php_zval_alloc();
514+
nr_php_zval_str(middleware, "newrelic\\Guzzle6\\middleware");
515+
if (!nr_php_is_zval_valid_callable(middleware TSRMLS_CC)) {
516+
nrl_verbosedebug(NRL_FRAMEWORK,
517+
"%s: middleware string is not considered callable",
518+
__func__);
519+
520+
nrm_force_add(NRTXN(unscoped_metrics),
521+
"Supportability/library/Guzzle 6/MiddlewareNotCallable", 0);
522+
523+
goto end;
524+
}
525+
526+
guzzle_client_ce = nr_php_find_class("guzzlehttp\\client" TSRMLS_CC);
527+
if (NULL == guzzle_client_ce) {
528+
nrl_verbosedebug(NRL_FRAMEWORK,
529+
"%s: unable to get class entry for GuzzleHttp\\Client",
530+
__func__);
531+
goto end;
532+
}
533+
534+
config = nr_php_get_zval_object_property_with_class(
535+
this_var, guzzle_client_ce, "config" TSRMLS_CC);
536+
if (!nr_php_is_zval_valid_array(config)) {
537+
goto end;
538+
}
539+
540+
handler_stack = nr_php_zend_hash_find(Z_ARRVAL_P(config), "handler");
541+
if (!nr_php_object_instanceof_class(handler_stack,
542+
"GuzzleHttp\\HandlerStack" TSRMLS_CC)) {
543+
goto end;
544+
}
545+
546+
retval = nr_php_call(handler_stack, "push", middleware);
547+
548+
nr_php_zval_free(&retval);
549+
550+
end:
551+
nr_php_zval_free(&middleware);
552+
nr_php_scope_release(&this_var);
553+
}
554+
NR_PHP_WRAPPER_END
555+
556+
557+
void nr_guzzle6_enable(TSRMLS_D) {
558+
int _retval;
559+
560+
if (0 == NRINI(guzzle_enabled)) {
561+
return;
562+
}
563+
564+
_retval = zend_eval_string(
565+
"namespace newrelic\\Guzzle6;"
566+
567+
"use Psr\\Http\\Message\\RequestInterface;"
568+
569+
"if (!function_exists('newrelic\\Guzzle6\\middleware')) {"
570+
" function middleware(callable $handler) {"
571+
" return function (RequestInterface $request, array $options) use "
572+
"($handler) {"
573+
574+
" foreach (newrelic_get_request_metadata('Guzzle 6') as $k => $v) {"
575+
" $request = $request->withHeader($k, $v);"
576+
" }"
577+
578+
579+
" $rh = new RequestHandler($request);"
580+
" $promise = $handler($request, $options);"
581+
" $promise->then([$rh, 'onFulfilled'], [$rh, 'onRejected']);"
582+
458583
" return $promise;"
459584
" };"
460585
" }"
461586
"}",
462587
NULL, "newrelic/Guzzle6" TSRMLS_CC);
463588

464-
if (SUCCESS == retval) {
589+
if (SUCCESS == _retval) {
465590
nr_php_wrap_user_function(NR_PSTR("GuzzleHttp\\Client::__construct"),
466591
nr_guzzle_client_construct TSRMLS_CC);
467592
} else {
@@ -504,4 +629,4 @@ void nr_guzzle6_minit(TSRMLS_D) {
504629
NR_UNUSED_TSRMLS;
505630
}
506631

507-
#endif /* 5.5.x */
632+
#endif /* 5.5.x */

agent/lib_guzzle6.h

+6-4
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,22 @@
66
/*
77
* This file contains exported functions for Guzzle 6.
88
*/
9-
#ifndef LIB_GUZZLE6_HDR
10-
#define LIB_GUZZLE6_HDR
9+
#ifndef LIB_GUZZLE7_HDR
10+
#define LIB_GUZZLE7_HDR
1111

1212
#include "php_includes.h"
1313

1414
/*
15-
* Purpose : Performs tasks that we need performed on MINIT in the Guzzle 6
15+
* Purpose : Performs tasks that we need performed on MINIT in the Guzzle 6 and 7
1616
* instrumentation.
1717
*/
1818
extern void nr_guzzle6_minit(TSRMLS_D);
19+
extern void nr_guzzle7_minit(TSRMLS_D);
1920

2021
/*
21-
* Purpose : Client::__construct() wrapper for Guzzle 6.
22+
* Purpose : Client::__construct() wrapper for Guzzle 6 and 7.
2223
*/
2324
extern NR_PHP_WRAPPER_PROTOTYPE(nr_guzzle6_client_construct);
25+
extern NR_PHP_WRAPPER_PROTOTYPE(nr_guzzle7_client_construct);
2426

2527
#endif /* LIB_GUZZLE4_HDR */

agent/php_execute.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,7 @@ static nr_library_table_t libraries[] = {
482482
*/
483483
{"Guzzle 4-5", "clientinterface.php", nr_guzzle4_enable},
484484
{"Guzzle 6", "handlerstack.php", nr_guzzle6_enable},
485-
485+
{"Guzzle 7", "handlerstack.php", nr_guzzle7_enable},
486486
{"MongoDB", "mongodb/src/client.php", nr_mongodb_enable},
487487

488488
/*

agent/php_minit.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636
#include "util_threads.h"
3737

3838
#include "fw_laravel.h"
39-
#include "lib_guzzle4.h"
4039

4140
static void php_newrelic_init_globals(zend_newrelic_globals* nrg) {
4241
if (nrunlikely(NULL == nrg)) {
@@ -671,11 +670,12 @@ PHP_MINIT_FUNCTION(newrelic) {
671670

672671
nr_guzzle4_minit(TSRMLS_C);
673672
nr_guzzle6_minit(TSRMLS_C);
673+
nr_guzzle7_minit(TSRMLS_C);
674674
nr_laravel_minit(TSRMLS_C);
675675
nr_php_set_opcode_handlers();
676676

677677
nrl_debug(NRL_INIT, "MINIT processing done");
678-
#if ZEND_MODULE_API_NO >= ZEND_8_0_X_API_NO /* PHP 7.4+ */
678+
#if ZEND_MODULE_API_NO >= ZEND_8_0_X_API_NO /* PHP 8.0+ */
679679
NR_PHP_PROCESS_GLOBALS(zend_offset) = zend_get_resource_handle(dummy);
680680
#else
681681
NR_PHP_PROCESS_GLOBALS(zend_offset) = zend_get_resource_handle(&dummy);

tests/integration/external/guzzle7/test_cat.php

+1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363
[{"name":"OtherTransactionTotalTime/php__FILE__"}, [1, "??", "??", "??", "??", "??"]],
6464
[{"name":"Supportability/library/Guzzle 4-5/detected"},
6565
[2, 0, 0, 0, 0, 0]],
66+
[{"name":"Supportability/library/Guzzle 6/detected"}, [1, 0, 0, 0, 0, 0]],
6667
[{"name":"Supportability/library/Guzzle 7/detected"}, [1, 0, 0, 0, 0, 0]],
6768
[{"name":"Supportability/Unsupported/curl_setopt/CURLOPT_HEADERFUNCTION/closure"},
6869
[3, 0, 0, 0, 0, 0]]

tests/integration/external/guzzle7/test_dt.php

-2
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,6 @@
7676

7777
/* Create URL. */
7878
$url = "http://" . make_tracing_url(realpath(dirname(__FILE__)) . '/../../../include/tracing_endpoint.php');
79-
//echo "\n";
80-
//echo $url."\n";
8179

8280
/* Use guzzle 7 to make an http request. */
8381
use GuzzleHttp\Client;

0 commit comments

Comments
 (0)