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,172 @@ 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
+
550
+ region_zval = nr_php_get_zval_object_property_with_class (this_obj , base_class ,
551
+ "region" );
552
+ if (nr_php_is_zval_non_empty_string (region_zval )) {
553
+ cloud_attrs -> cloud_region = Z_STRVAL_P (region_zval );
554
+ }
555
+
556
+ endpoint_zval = nr_php_get_zval_object_property_with_class (
557
+ this_obj , base_class , "endpoint" );
558
+ if (nr_php_is_zval_valid_object (endpoint_zval )) {
559
+ host_zval = nr_php_get_zval_object_property (endpoint_zval , "host" );
560
+ if (nr_php_is_zval_non_empty_string (host_zval )) {
561
+ datastore_params -> instance -> host = Z_STRVAL_P (host_zval );
562
+
563
+ /* Only try to get a port if we have a valid host. */
564
+ port_zval = nr_php_get_zval_object_property (endpoint_zval , "port" );
565
+ if (nr_php_is_zval_valid_integer (port_zval )) {
566
+ /* Must be freed by caller */
567
+ datastore_params -> instance -> port_path_or_id
568
+ = nr_formatf (NR_INT64_FMT , Z_LVAL_P (port_zval ));
569
+ } else {
570
+ /* In case where host was found but port was not, spec says return
571
+ * unknown for port. */
572
+ datastore_params -> instance -> port_path_or_id = nr_strdup ("unknown" );
573
+ }
574
+ }
575
+ }
576
+
577
+ if (NULL == datastore_params -> instance -> host ) {
578
+ /* Unable to retrieve the endpoint, go with AWS defaults. */
579
+ datastore_params -> instance -> host = AWS_SDK_PHP_DYNAMODBCLIENT_DEFAULT_HOST ;
580
+ /* Need to strdup because the calling function will free it. */
581
+ datastore_params -> instance -> port_path_or_id
582
+ = nr_strdup (AWS_SDK_PHP_DYNAMODBCLIENT_DEFAULT_PORT );
583
+ }
584
+
585
+ table_name = nr_lib_aws_sdk_php_get_command_arg_value (
586
+ AWS_SDK_PHP_DYNAMODBCLIENT_TABLENAME_ARG , NR_EXECUTE_ORIG_ARGS );
587
+ if (!nr_strempty (table_name )) {
588
+ /* Must be freed by caller */
589
+ datastore_params -> collection = table_name ;
590
+ }
591
+ if (!nr_strempty (NRINI (aws_account_id ))) {
592
+ account_id = NRINI (aws_account_id );
593
+ }
594
+
595
+ if (NULL != datastore_params -> collection && NULL != account_id
596
+ && NULL != cloud_attrs -> cloud_region ) {
597
+ /* Must be freed by caller */
598
+ cloud_attrs -> cloud_resource_id = nr_formatf (
599
+ "arn:aws:dynamodb:%s:%s:table/%s" , cloud_attrs -> cloud_region ,
600
+ account_id , datastore_params -> collection );
601
+ }
602
+ }
603
+
604
+ void nr_lib_aws_sdk_php_dynamodb_handle (nr_segment_t * auto_segment ,
605
+ char * command_name_string ,
606
+ size_t command_name_len ,
607
+ NR_EXECUTE_PROTO ) {
608
+ nr_segment_t * datastore_segment = NULL ;
609
+ nr_segment_cloud_attrs_t cloud_attrs = {0 };
610
+ nr_datastore_instance_t instance = {0 };
611
+ nr_segment_datastore_params_t datastore_params = {
612
+ .db_system = AWS_SDK_PHP_DYNAMODBCLIENT_DATASTORE_SYSTEM ,
613
+ .datastore = {
614
+ .type = NR_DATASTORE_DYNAMODB ,
615
+ },
616
+ .instance = & instance ,
617
+ .callbacks = {
618
+ .backtrace = nr_php_backtrace_callback ,
619
+ },
620
+ };
621
+ if (NULL == auto_segment ) {
622
+ return ;
623
+ }
624
+
625
+ if (NULL == command_name_string || 0 == command_name_len ) {
626
+ return ;
627
+ }
628
+
629
+ #define AWS_COMMAND_IS (CMD ) \
630
+ (command_name_len == (sizeof(CMD) - 1) && nr_streq(CMD, command_name_string))
631
+
632
+ /* Determine if we instrument this command. */
633
+ if (AWS_COMMAND_IS ("createTable" )) {
634
+ datastore_params .operation = AWS_SDK_PHP_DYNAMODBCLIENT_CREATE_TABLE ;
635
+ } else if (AWS_COMMAND_IS ("deleteItem" )) {
636
+ datastore_params .operation = AWS_SDK_PHP_DYNAMODBCLIENT_DELETE_ITEM ;
637
+ } else if (AWS_COMMAND_IS ("deleteTable" )) {
638
+ datastore_params .operation = AWS_SDK_PHP_DYNAMODBCLIENT_DELETE_TABLE ;
639
+ } else if (AWS_COMMAND_IS ("getItem" )) {
640
+ datastore_params .operation = AWS_SDK_PHP_DYNAMODBCLIENT_GET_ITEM ;
641
+ } else if (AWS_COMMAND_IS ("putItem" )) {
642
+ datastore_params .operation = AWS_SDK_PHP_DYNAMODBCLIENT_PUT_ITEM ;
643
+ } else if (AWS_COMMAND_IS ("query" )) {
644
+ datastore_params .operation = AWS_SDK_PHP_DYNAMODBCLIENT_QUERY ;
645
+ } else if (AWS_COMMAND_IS ("scan" )) {
646
+ datastore_params .operation = AWS_SDK_PHP_DYNAMODBCLIENT_SCAN ;
647
+ } else if (AWS_COMMAND_IS ("updateItem" )) {
648
+ datastore_params .operation = AWS_SDK_PHP_DYNAMODBCLIENT_UPDATE_ITEM ;
649
+ } else {
650
+ /* Nothing to do here so exit. */
651
+ return ;
652
+ }
653
+ #undef AWS_COMMAND_IS
654
+
655
+ /*
656
+ * nr_lib_aws_sdk_php_dynamodb_set_params sets:
657
+ * the cloud_attrs->region and cloud_resource_id(needs to be freed)
658
+ * datastore->instance host and port_path_or_id(needs to be freed)
659
+ * datastore->collection (needs to be freed)
660
+ */
661
+ nr_lib_aws_sdk_php_dynamodb_set_params (& datastore_params , & cloud_attrs ,
662
+ NR_EXECUTE_ORIG_ARGS );
663
+
664
+ /*
665
+ * By this point, the datastore params are decoded, grab the parent segment
666
+ * start time, add the special segment attributes/metrics then close the newly
667
+ * created segment.
668
+ */
669
+ datastore_segment = nr_segment_start (NRPRG (txn ), NULL , NULL );
670
+ if (NULL == datastore_segment ) {
671
+ return ;
672
+ }
673
+ /* re-use start time from auto_segment started in func_begin */
674
+ datastore_segment -> start_time = auto_segment -> start_time ;
675
+ cloud_attrs .aws_operation = command_name_string ;
676
+
677
+ /* Add cloud attributes, if available. */
678
+ nr_segment_traces_add_cloud_attributes (datastore_segment , & cloud_attrs );
679
+
680
+ /* Now end the instrumented segment as a message segment. */
681
+ nr_segment_datastore_end (& datastore_segment , & datastore_params );
682
+ nr_free (datastore_params .collection );
683
+ nr_free (cloud_attrs .cloud_resource_id );
684
+ nr_free (instance .port_path_or_id );
685
+ }
686
+
522
687
/*
523
688
* For Aws/AwsClient::__call see
524
689
* https://github.com/aws/aws-sdk-php/blob/master/src/AwsClientInterface.php
@@ -580,8 +745,12 @@ NR_PHP_WRAPPER(nr_aws_client_call) {
580
745
NR_EXECUTE_ORIG_ARGS );
581
746
} else if (AWS_CLASS_IS ("Aws\\Lambda\\LambdaClient" , "LambdaClient" )) {
582
747
nr_lib_aws_sdk_php_lambda_handle (auto_segment , command_name_string ,
583
- Z_STRLEN_P (command_name ),
584
- NR_EXECUTE_ORIG_ARGS );
748
+ Z_STRLEN_P (command_name ),
749
+ NR_EXECUTE_ORIG_ARGS );
750
+ } else if (AWS_CLASS_IS ("Aws\\DynamoDb\\DynamoDbClient" , "DynamoDbClient" )) {
751
+ nr_lib_aws_sdk_php_dynamodb_handle (auto_segment , command_name_string ,
752
+ Z_STRLEN_P (command_name ),
753
+ NR_EXECUTE_ORIG_ARGS );
585
754
}
586
755
587
756
#undef AWS_CLASS_IS
0 commit comments