14
14
#include "fw_hooks.h"
15
15
#include "fw_support.h"
16
16
#include "util_logging.h"
17
- #include "nr_segment_message.h"
18
- #include "nr_segment_external.h"
19
17
#include "lib_aws_sdk_php.h"
20
18
21
19
#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-]+))?"
28
27
29
28
#if ZEND_MODULE_API_NO >= ZEND_8_1_X_API_NO /* PHP8.1+ */
30
29
/* 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,
309
308
nr_segment_t * external_segment = NULL ;
310
309
zval * * retval_ptr = NR_GET_RETURN_VALUE_PTR ;
311
310
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" };
315
312
316
313
if (NULL == auto_segment ) {
317
314
return ;
@@ -332,7 +329,8 @@ void nr_lib_aws_sdk_php_lambda_handle(nr_segment_t* auto_segment,
332
329
/* Determine if we instrument this command. */
333
330
if (AWS_COMMAND_IS ("invoke" )) {
334
331
/* 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 );
336
334
} else {
337
335
return ;
338
336
}
@@ -362,7 +360,7 @@ void nr_lib_aws_sdk_php_lambda_handle(nr_segment_t* auto_segment,
362
360
external_params .status = Z_LVAL_P (status_code );
363
361
}
364
362
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 )) {
366
364
metadata = Z_REFVAL_P (metadata );
367
365
}
368
366
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,
371
369
external_params .uri = Z_STRVAL_P (uri );
372
370
}
373
371
}
374
-
375
372
}
376
373
nr_segment_external_end (& external_segment , & external_params );
377
374
nr_free (cloud_attrs .cloud_resource_id );
378
375
}
379
376
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
382
379
*/
383
380
static nr_regex_t * aws_arn_regex ;
384
381
@@ -390,7 +387,9 @@ void nr_aws_sdk_mshutdown(void) {
390
387
nr_regex_destroy (& aws_arn_regex );
391
388
}
392
389
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 ) {
394
393
zval * call_args = nr_php_get_user_func_arg (2 , NR_EXECUTE_ORIG_ARGS );
395
394
zval * this_obj = NR_PHP_USER_FN_THIS ();
396
395
char * arn = NULL ;
@@ -409,7 +408,8 @@ void nr_aws_sdk_lambda_client_invoke_parse_args(NR_EXECUTE_PROTO, nr_segment_clo
409
408
if (!nr_php_is_zval_valid_array (lambda_args )) {
410
409
return ;
411
410
}
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" );
413
413
if (!nr_php_is_zval_non_empty_string (lambda_name )) {
414
414
return ;
415
415
}
@@ -420,10 +420,8 @@ void nr_aws_sdk_lambda_client_invoke_parse_args(NR_EXECUTE_PROTO, nr_segment_clo
420
420
}
421
421
422
422
/* 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 ));
427
425
function_name = nr_regex_substrings_get_named (matches , "functionName" );
428
426
accountID = nr_regex_substrings_get_named (matches , "accountId" );
429
427
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
449
447
}
450
448
if (nr_strempty (region )) {
451
449
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 ;
454
453
}
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" );
457
456
if (nr_php_is_zval_valid_string (region_zval )) {
458
457
/*
459
458
* 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
467
466
if (!nr_strempty (accountID ) && !nr_strempty (region )) {
468
467
/* construct the ARN */
469
468
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 );
472
471
} 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 );
475
474
}
476
475
477
476
/* Attach the ARN */
@@ -519,6 +518,170 @@ char* nr_lib_aws_sdk_php_get_command_arg_value(char* command_arg_name,
519
518
return command_arg_value ;
520
519
}
521
520
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
+
522
685
/*
523
686
* For Aws/AwsClient::__call see
524
687
* https://github.com/aws/aws-sdk-php/blob/master/src/AwsClientInterface.php
@@ -580,8 +743,12 @@ NR_PHP_WRAPPER(nr_aws_client_call) {
580
743
NR_EXECUTE_ORIG_ARGS );
581
744
} else if (AWS_CLASS_IS ("Aws\\Lambda\\LambdaClient" , "LambdaClient" )) {
582
745
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 );
585
752
}
586
753
587
754
#undef AWS_CLASS_IS
0 commit comments