1414#include "fw_hooks.h"
1515#include "fw_support.h"
1616#include "util_logging.h"
17- #include "nr_segment_message.h"
18- #include "nr_segment_external.h"
1917#include "lib_aws_sdk_php.h"
2018
2119#define PHP_PACKAGE_NAME "aws/aws-sdk-php"
22- #define AWS_LAMBDA_ARN_REGEX "(arn:(aws[a-zA-Z-]*)?:lambda:)?" \
23- "((?<region>[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}):)?" \
24- "((?<accountId>\\d{12}):)?" \
25- "(function:)?" \
26- "(?<functionName>[a-zA-Z0-9-\\.]+)" \
27- "(:(?<qualifier>\\$LATEST|[a-zA-Z0-9-]+))?"
20+ #define AWS_LAMBDA_ARN_REGEX \
21+ "(arn:(aws[a-zA-Z-]*)?:lambda:)?" \
22+ "((?<region>[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}):)?" \
23+ "((?<accountId>\\d{12}):)?" \
24+ "(function:)?" \
25+ "(?<functionName>[a-zA-Z0-9-\\.]+)" \
26+ "(:(?<qualifier>\\$LATEST|[a-zA-Z0-9-]+))?"
2827
2928#if ZEND_MODULE_API_NO >= ZEND_8_1_X_API_NO /* PHP8.1+ */
3029/* Service instrumentation only supported above PHP 8.1+*/
@@ -309,9 +308,7 @@ void nr_lib_aws_sdk_php_lambda_handle(nr_segment_t* auto_segment,
309308 nr_segment_t * external_segment = NULL ;
310309 zval * * retval_ptr = NR_GET_RETURN_VALUE_PTR ;
311310
312- nr_segment_cloud_attrs_t cloud_attrs = {
313- .cloud_platform = "aws_lambda"
314- };
311+ nr_segment_cloud_attrs_t cloud_attrs = {.cloud_platform = "aws_lambda" };
315312
316313 if (NULL == auto_segment ) {
317314 return ;
@@ -332,7 +329,8 @@ void nr_lib_aws_sdk_php_lambda_handle(nr_segment_t* auto_segment,
332329 /* Determine if we instrument this command. */
333330 if (AWS_COMMAND_IS ("invoke" )) {
334331 /* reconstruct the ARN */
335- nr_aws_sdk_lambda_client_invoke_parse_args (NR_EXECUTE_ORIG_ARGS , & cloud_attrs );
332+ nr_aws_sdk_lambda_client_invoke_parse_args (NR_EXECUTE_ORIG_ARGS ,
333+ & cloud_attrs );
336334 } else {
337335 return ;
338336 }
@@ -362,7 +360,7 @@ void nr_lib_aws_sdk_php_lambda_handle(nr_segment_t* auto_segment,
362360 external_params .status = Z_LVAL_P (status_code );
363361 }
364362 zval * metadata = nr_php_zend_hash_find (Z_ARRVAL_P (data ), "@metadata" );
365- if (NULL != metadata && IS_REFERENCE == Z_TYPE_P (metadata )) {
363+ if (NULL != metadata && IS_REFERENCE == Z_TYPE_P (metadata )) {
366364 metadata = Z_REFVAL_P (metadata );
367365 }
368366 if (nr_php_is_zval_valid_array (metadata )) {
@@ -371,14 +369,13 @@ void nr_lib_aws_sdk_php_lambda_handle(nr_segment_t* auto_segment,
371369 external_params .uri = Z_STRVAL_P (uri );
372370 }
373371 }
374-
375372 }
376373 nr_segment_external_end (& external_segment , & external_params );
377374 nr_free (cloud_attrs .cloud_resource_id );
378375}
379376
380- /* This stores the compiled regex to parse AWS ARNs. The compilation happens when
381- * it is first needed and is destroyed in mshutdown
377+ /* This stores the compiled regex to parse AWS ARNs. The compilation happens
378+ * when it is first needed and is destroyed in mshutdown
382379 */
383380static nr_regex_t * aws_arn_regex ;
384381
@@ -390,7 +387,9 @@ void nr_aws_sdk_mshutdown(void) {
390387 nr_regex_destroy (& aws_arn_regex );
391388}
392389
393- void nr_aws_sdk_lambda_client_invoke_parse_args (NR_EXECUTE_PROTO , nr_segment_cloud_attrs_t * cloud_attrs ) {
390+ void nr_aws_sdk_lambda_client_invoke_parse_args (
391+ NR_EXECUTE_PROTO ,
392+ nr_segment_cloud_attrs_t * cloud_attrs ) {
394393 zval * call_args = nr_php_get_user_func_arg (2 , NR_EXECUTE_ORIG_ARGS );
395394 zval * this_obj = NR_PHP_USER_FN_THIS ();
396395 char * arn = NULL ;
@@ -409,7 +408,8 @@ void nr_aws_sdk_lambda_client_invoke_parse_args(NR_EXECUTE_PROTO, nr_segment_clo
409408 if (!nr_php_is_zval_valid_array (lambda_args )) {
410409 return ;
411410 }
412- zval * lambda_name = nr_php_zend_hash_find (Z_ARRVAL_P (lambda_args ), "FunctionName" );
411+ zval * lambda_name
412+ = nr_php_zend_hash_find (Z_ARRVAL_P (lambda_args ), "FunctionName" );
413413 if (!nr_php_is_zval_non_empty_string (lambda_name )) {
414414 return ;
415415 }
@@ -420,10 +420,8 @@ void nr_aws_sdk_lambda_client_invoke_parse_args(NR_EXECUTE_PROTO, nr_segment_clo
420420 }
421421
422422 /* Extract all information possible from the passed lambda name via regex */
423- nr_regex_substrings_t * matches =
424- nr_regex_match_capture (aws_arn_regex ,
425- Z_STRVAL_P (lambda_name ),
426- Z_STRLEN_P (lambda_name ));
423+ nr_regex_substrings_t * matches = nr_regex_match_capture (
424+ aws_arn_regex , Z_STRVAL_P (lambda_name ), Z_STRLEN_P (lambda_name ));
427425 function_name = nr_regex_substrings_get_named (matches , "functionName" );
428426 accountID = nr_regex_substrings_get_named (matches , "accountId" );
429427 region = nr_regex_substrings_get_named (matches , "region" );
@@ -449,11 +447,12 @@ void nr_aws_sdk_lambda_client_invoke_parse_args(NR_EXECUTE_PROTO, nr_segment_clo
449447 }
450448 if (nr_strempty (region )) {
451449 zend_class_entry * base_class = NULL ;
452- if (NULL != execute_data -> func && NULL != execute_data -> func -> common .scope ) {
453- base_class = execute_data -> func -> common .scope ;
450+ if (NULL != execute_data -> func
451+ && NULL != execute_data -> func -> common .scope ) {
452+ base_class = execute_data -> func -> common .scope ;
454453 }
455- region_zval
456- = nr_php_get_zval_object_property_with_class ( this_obj , base_class , "region" );
454+ region_zval = nr_php_get_zval_object_property_with_class (
455+ this_obj , base_class , "region" );
457456 if (nr_php_is_zval_valid_string (region_zval )) {
458457 /*
459458 * In this case, region is likely to be NULL, but could be an empty
@@ -467,11 +466,11 @@ void nr_aws_sdk_lambda_client_invoke_parse_args(NR_EXECUTE_PROTO, nr_segment_clo
467466 if (!nr_strempty (accountID ) && !nr_strempty (region )) {
468467 /* construct the ARN */
469468 if (!nr_strempty (qualifier )) {
470- arn = nr_formatf ("arn:aws:lambda:%s:%s:function:%s:%s" ,
471- region , accountID , function_name , qualifier );
469+ arn = nr_formatf ("arn:aws:lambda:%s:%s:function:%s:%s" , region , accountID ,
470+ function_name , qualifier );
472471 } else {
473- arn = nr_formatf ("arn:aws:lambda:%s:%s:function:%s" ,
474- region , accountID , function_name );
472+ arn = nr_formatf ("arn:aws:lambda:%s:%s:function:%s" , region , accountID ,
473+ function_name );
475474 }
476475
477476 /* Attach the ARN */
@@ -519,6 +518,170 @@ char* nr_lib_aws_sdk_php_get_command_arg_value(char* command_arg_name,
519518 return command_arg_value ;
520519}
521520
521+ void nr_lib_aws_sdk_php_dynamodb_set_params (
522+ nr_segment_datastore_params_t * datastore_params ,
523+ nr_segment_cloud_attrs_t * cloud_attrs ,
524+ NR_EXECUTE_PROTO ) {
525+ zval * endpoint_zval = NULL ;
526+ zval * region_zval = NULL ;
527+ zval * host_zval = NULL ;
528+ zval * port_zval = NULL ;
529+ zval * this_obj = NULL ;
530+ zend_function * func = NULL ;
531+ zend_class_entry * base_class = NULL ;
532+ char * table_name = NULL ;
533+ char * account_id = NULL ;
534+
535+ if (NULL == datastore_params || NULL == cloud_attrs ) {
536+ return ;
537+ }
538+
539+ this_obj = NR_PHP_USER_FN_THIS ();
540+ func = nr_php_execute_function (NR_EXECUTE_ORIG_ARGS );
541+
542+ if (NULL == this_obj || NULL == func ) {
543+ return ;
544+ }
545+
546+ if (NULL != func -> common .scope ) {
547+ base_class = func -> common .scope ;
548+
549+ region_zval = nr_php_get_zval_object_property_with_class (
550+ this_obj , base_class , "region" );
551+ if (nr_php_is_zval_non_empty_string (region_zval )) {
552+ cloud_attrs -> cloud_region = Z_STRVAL_P (region_zval );
553+ }
554+
555+ endpoint_zval = nr_php_get_zval_object_property_with_class (
556+ this_obj , base_class , "endpoint" );
557+ if (nr_php_is_zval_valid_object (endpoint_zval )) {
558+ host_zval = nr_php_get_zval_object_property (endpoint_zval , "host" );
559+ if (nr_php_is_zval_non_empty_string (host_zval )) {
560+ datastore_params -> instance -> host = Z_STRVAL_P (host_zval );
561+
562+ /* Only try to get a port if we have a valid host. */
563+ port_zval = nr_php_get_zval_object_property (endpoint_zval , "port" );
564+ if (nr_php_is_zval_valid_integer (port_zval )) {
565+ /* Must be freed by caller */
566+ datastore_params -> instance -> port_path_or_id
567+ = nr_formatf (NR_INT64_FMT , Z_LVAL_P (port_zval ));
568+ } else {
569+ /* In case where host was found but port was not, spec says return
570+ * unknown for port. */
571+ datastore_params -> instance -> port_path_or_id = nr_strdup ("unknown" );
572+ }
573+ }
574+ }
575+ }
576+ if (NULL == datastore_params -> instance -> host ) {
577+ /* Unable to retrieve the endpoint, go with AWS defaults. */
578+ datastore_params -> instance -> host = AWS_SDK_PHP_DYNAMODBCLIENT_DEFAULT_HOST ;
579+ /* Need to strdup because the calling function will free it. */
580+ datastore_params -> instance -> port_path_or_id
581+ = nr_strdup (AWS_SDK_PHP_DYNAMODBCLIENT_DEFAULT_PORT );
582+ }
583+
584+ table_name = nr_lib_aws_sdk_php_get_command_arg_value (
585+ AWS_SDK_PHP_DYNAMODBCLIENT_TABLENAME_ARG , NR_EXECUTE_ORIG_ARGS );
586+ if (!nr_strempty (table_name )) {
587+ /* Must be freed by caller */
588+ datastore_params -> collection = table_name ;
589+ }
590+ if (!nr_strempty (NRINI (aws_account_id ))) {
591+ account_id = NRINI (aws_account_id );
592+ }
593+
594+ if (NULL != datastore_params -> collection && NULL != account_id
595+ && NULL != cloud_attrs -> cloud_region ) {
596+ /* Must be freed by caller */
597+ cloud_attrs -> cloud_resource_id = nr_formatf (
598+ "arn:aws:dynamodb:%s:%s:table/%s" , cloud_attrs -> cloud_region ,
599+ account_id , datastore_params -> collection );
600+ }
601+ }
602+
603+ void nr_lib_aws_sdk_php_dynamodb_handle (nr_segment_t * auto_segment ,
604+ char * command_name_string ,
605+ size_t command_name_len ,
606+ NR_EXECUTE_PROTO ) {
607+ nr_segment_t * datastore_segment = NULL ;
608+ nr_segment_cloud_attrs_t cloud_attrs = {0 };
609+ nr_datastore_instance_t instance = {0 };
610+ nr_segment_datastore_params_t datastore_params = {
611+ .db_system = AWS_SDK_PHP_DYNAMODBCLIENT_DATASTORE_SYSTEM ,
612+ .datastore = {
613+ .type = NR_DATASTORE_DYNAMODB ,
614+ },
615+ .instance = & instance ,
616+ .callbacks = {
617+ .backtrace = nr_php_backtrace_callback ,
618+ },
619+ };
620+ if (NULL == auto_segment ) {
621+ return ;
622+ }
623+
624+ if (NULL == command_name_string || 0 == command_name_len ) {
625+ return ;
626+ }
627+
628+ #define AWS_COMMAND_IS (CMD ) \
629+ (command_name_len == (sizeof(CMD) - 1) && nr_streq(CMD, command_name_string))
630+
631+ /* Determine if we instrument this command. */
632+ if (AWS_COMMAND_IS ("createTable" )) {
633+ datastore_params .operation = AWS_SDK_PHP_DYNAMODBCLIENT_CREATE_TABLE ;
634+ } else if (AWS_COMMAND_IS ("deleteItem" )) {
635+ datastore_params .operation = AWS_SDK_PHP_DYNAMODBCLIENT_DELETE_ITEM ;
636+ } else if (AWS_COMMAND_IS ("deleteTable" )) {
637+ datastore_params .operation = AWS_SDK_PHP_DYNAMODBCLIENT_DELETE_TABLE ;
638+ } else if (AWS_COMMAND_IS ("getItem" )) {
639+ datastore_params .operation = AWS_SDK_PHP_DYNAMODBCLIENT_GET_ITEM ;
640+ } else if (AWS_COMMAND_IS ("putItem" )) {
641+ datastore_params .operation = AWS_SDK_PHP_DYNAMODBCLIENT_PUT_ITEM ;
642+ } else if (AWS_COMMAND_IS ("query" )) {
643+ datastore_params .operation = AWS_SDK_PHP_DYNAMODBCLIENT_QUERY ;
644+ } else if (AWS_COMMAND_IS ("scan" )) {
645+ datastore_params .operation = AWS_SDK_PHP_DYNAMODBCLIENT_SCAN ;
646+ } else if (AWS_COMMAND_IS ("updateItem" )) {
647+ datastore_params .operation = AWS_SDK_PHP_DYNAMODBCLIENT_UPDATE_ITEM ;
648+ } else {
649+ /* Nothing to do here so exit. */
650+ return ;
651+ }
652+ #undef AWS_COMMAND_IS
653+
654+ /*
655+ * nr_lib_aws_sdk_php_dynamodb_set_params sets:
656+ * the cloud_attrs->region and cloud_resource_id(needs to be freed)
657+ * datastore->instance host and port_path_or_id(needs to be freed)
658+ * datastore->collection (needs to be freed)
659+ */
660+ nr_lib_aws_sdk_php_dynamodb_set_params (& datastore_params , & cloud_attrs ,
661+ NR_EXECUTE_ORIG_ARGS );
662+
663+ /*
664+ * By this point, the datastore params are decoded, grab the parent segment
665+ * start time, add the special segment attributes/metrics then close the newly
666+ * created segment.
667+ */
668+ datastore_segment = nr_segment_start (NRPRG (txn ), NULL , NULL );
669+ if (NULL != datastore_segment ) {
670+ /* re-use start time from auto_segment started in func_begin */
671+ datastore_segment -> start_time = auto_segment -> start_time ;
672+ cloud_attrs .aws_operation = command_name_string ;
673+
674+ /* Add cloud attributes, if available. */
675+ nr_segment_traces_add_cloud_attributes (datastore_segment , & cloud_attrs );
676+
677+ /* Now end the instrumented segment as a message segment. */
678+ nr_segment_datastore_end (& datastore_segment , & datastore_params );
679+ }
680+ nr_free (datastore_params .collection );
681+ nr_free (cloud_attrs .cloud_resource_id );
682+ nr_free (instance .port_path_or_id );
683+ }
684+
522685/*
523686 * For Aws/AwsClient::__call see
524687 * https://github.com/aws/aws-sdk-php/blob/master/src/AwsClientInterface.php
@@ -580,8 +743,12 @@ NR_PHP_WRAPPER(nr_aws_client_call) {
580743 NR_EXECUTE_ORIG_ARGS );
581744 } else if (AWS_CLASS_IS ("Aws\\Lambda\\LambdaClient" , "LambdaClient" )) {
582745 nr_lib_aws_sdk_php_lambda_handle (auto_segment , command_name_string ,
583- Z_STRLEN_P (command_name ),
584- NR_EXECUTE_ORIG_ARGS );
746+ Z_STRLEN_P (command_name ),
747+ NR_EXECUTE_ORIG_ARGS );
748+ } else if (AWS_CLASS_IS ("Aws\\DynamoDb\\DynamoDbClient" , "DynamoDbClient" )) {
749+ nr_lib_aws_sdk_php_dynamodb_handle (auto_segment , command_name_string ,
750+ Z_STRLEN_P (command_name ),
751+ NR_EXECUTE_ORIG_ARGS );
585752 }
586753
587754#undef AWS_CLASS_IS
0 commit comments