From b51fbadbd3592b438c7514fe56d75f36c2ca6a2a Mon Sep 17 00:00:00 2001 From: Zach Neumann Date: Fri, 7 Feb 2025 12:08:05 -0700 Subject: [PATCH 01/18] lambda instrumentation --- agent/lib_aws_sdk_php.c | 173 ++++++++++++++++++++++++++++++++++++++ agent/lib_aws_sdk_php.h | 12 +++ agent/php_newrelic.h | 9 +- agent/php_nrini.c | 12 +++ axiom/nr_segment.h | 2 + axiom/nr_segment_traces.c | 5 ++ axiom/nr_span_event.h | 1 + 7 files changed, 213 insertions(+), 1 deletion(-) diff --git a/agent/lib_aws_sdk_php.c b/agent/lib_aws_sdk_php.c index 46a71ce3c..285d669f6 100644 --- a/agent/lib_aws_sdk_php.c +++ b/agent/lib_aws_sdk_php.c @@ -15,9 +15,16 @@ #include "fw_support.h" #include "util_logging.h" #include "nr_segment_message.h" +#include "nr_segment_external.h" #include "lib_aws_sdk_php.h" #define PHP_PACKAGE_NAME "aws/aws-sdk-php" +#define AWS_ARN_REGEX "(arn:(aws[a-zA-Z-]*)?:lambda:)?" \ + "((?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}):)?" \ + "((?\\d{12}):)?" \ + "(function:)?" \ + "(?[a-zA-Z0-9-\\.]+)" \ + "(:(?\\$LATEST|[a-zA-Z0-9-]+))?" #if ZEND_MODULE_API_NO >= ZEND_8_1_X_API_NO /* PHP8.1+ */ /* Service instrumentation only supported above PHP 8.1+*/ @@ -295,6 +302,167 @@ void nr_lib_aws_sdk_php_sqs_parse_queueurl( cloud_attrs->cloud_region = region; } +void nr_lib_aws_sdk_php_lambda_handle(nr_segment_t* auto_segment, + char* command_name_string, + size_t command_name_len, + NR_EXECUTE_PROTO) { + nr_segment_t* external_segment = NULL; + zval** retval_ptr = NR_GET_RETURN_VALUE_PTR; + + nr_segment_cloud_attrs_t cloud_attrs = { + .cloud_platform = "aws_lambda" + }; + + if (NULL == auto_segment) { + return; + } + + if (NULL == command_name_string || 0 == command_name_len) { + return; + } + + if (NULL == retval_ptr) { + /* Do not instrument when an exception has happened */ + return; + } + +#define AWS_COMMAND_IS(CMD) \ + (command_name_len == (sizeof(CMD) - 1) && nr_streq(CMD, command_name_string)) + + /* Determine if we instrument this command. */ + if (AWS_COMMAND_IS("invoke")) { + // Command can be saved here if in the future + // we instrument more than 1 lambda command + } else { + return; + } +#undef AWS_COMMAND_IS + + /* reconstruct the ARN */ + nr_aws_lambda_invoke(NR_EXECUTE_ORIG_ARGS, &cloud_attrs); + if (!cloud_attrs.cloud_resource_id) { + /* we do not want to instrument if we cannot reconstruct the ARN */ + return; + } + + /* + * By this point, it's been determined that this call will be instrumented so + * only create the segment now, grab the parent segment start time, add our + * special segment attributes/metrics then close the newly created segment. + */ + external_segment = nr_segment_start(NRPRG(txn), NULL, NULL); + if (NULL == external_segment) { + return; + } + /* re-use start time from auto_segment started in func_begin */ + external_segment->start_time = auto_segment->start_time; + cloud_attrs.aws_operation = command_name_string; + + /* end the segment */ + nr_segment_traces_add_cloud_attributes(external_segment, &cloud_attrs); + nr_segment_external_params_t external_params = {.library = "aws_sdk"}; + zval* data = nr_php_get_zval_object_property(*retval_ptr, "data"); + if (NULL != data && IS_ARRAY == Z_TYPE_P(data)) { + zval* status_code = nr_php_zend_hash_find(Z_ARRVAL_P(data), "StatusCode"); + if (NULL != status_code && IS_LONG == Z_TYPE_P(status_code)) { + external_params.status = Z_LVAL_P(status_code); + } + zval* metadata = nr_php_zend_hash_find(Z_ARRVAL_P(data), "@metadata"); + if (NULL != metadata && IS_REFERENCE == Z_TYPE_P(metadata) + && IS_ARRAY == Z_TYPE_P(Z_REFVAL_P(metadata))) { + zval* uri = nr_php_zend_hash_find(Z_ARRVAL_P(Z_REFVAL_P(metadata)), "effectiveUri"); + if (NULL != uri && IS_STRING == Z_TYPE_P(uri)) { + external_params.uri = Z_STRVAL_P(uri); + } + } + } + nr_segment_external_end(&auto_segment, &external_params); +} + +void nr_aws_lambda_invoke(NR_EXECUTE_PROTO, nr_segment_cloud_attrs_t* cloud_attrs) { + zval* call_args = nr_php_get_user_func_arg(2, NR_EXECUTE_ORIG_ARGS); + zval* this_obj = NR_PHP_USER_FN_THIS(); + char* arn = NULL; + char* function_name = NULL; + char* region = NULL; + zval* region_zval = NULL; + char* qualifier = NULL; + zval* qualifier_zval = NULL; + char* accountID = NULL; + + /* verify arguments */ + if (NULL == call_args || IS_ARRAY != Z_TYPE_P(call_args)) { + return; + } + zval* lambda_args = nr_php_zend_hash_index_find(Z_ARRVAL_P(call_args), 0); + if (NULL == lambda_args || IS_ARRAY != Z_TYPE_P(lambda_args)) { + return; + } + zval* lambda_name = nr_php_zend_hash_find(Z_ARRVAL_P(lambda_args), "FunctionName"); + if (NULL == lambda_name || IS_STRING != Z_TYPE_P(lambda_name)) { + return; + } + + /* Compile the regex */ + if (NULL == NRPRG(aws_arn_regex)) { + NRPRG(aws_arn_regex) = nr_regex_create(AWS_ARN_REGEX, 0, 0); + } + + /* Extract all information possible from the passed lambda name via regex */ + nr_regex_substrings_t* matches = + nr_regex_match_capture(NRPRG(aws_arn_regex), + Z_STRVAL_P(lambda_name), + Z_STRLEN_P(lambda_name)); + function_name = nr_regex_substrings_get_named(matches, "functionName"); + accountID = nr_regex_substrings_get_named(matches, "accountId"); + region = nr_regex_substrings_get_named(matches, "region"); + qualifier = nr_regex_substrings_get_named(matches, "qualifier"); + + /* suppliment missing information with API calls */ + if (nr_strempty(function_name)) { + /* + * Cannot get the needed data. Function name is required in the + * argument, so this won't happen in normal operation + */ + nr_regex_substrings_destroy(&matches); + return; + } + if (nr_strempty(accountID)) { + accountID = NRINI(aws_account_id); + accountID = "012345"; + } + if (nr_strempty(region)) { + region_zval = nr_php_call(this_obj, "getRegion"); + if (nr_php_is_zval_valid_string(region_zval)) { + region = Z_STRVAL_P(region_zval); + } + } + if (nr_strempty(qualifier)) { + qualifier_zval = NULL;//nr_php_call(this_obj, "getQualifier"); + if (nr_php_is_zval_valid_string(qualifier_zval)) { + qualifier = Z_STRVAL_P(qualifier_zval); + } + } + + if (!nr_strempty(accountID) && !nr_strempty(region)) { + // construct the ARN + if (qualifier) { + arn = nr_formatf("arn:aws:lambda:%s:%s:function:%s:%s", + region, accountID, function_name, qualifier); + } else { + arn = nr_formatf("arn:aws:lambda:%s:%s:function:%s", + region, accountID, function_name); + } + + // Attatch the ARN + cloud_attrs->cloud_resource_id = arn; + } + + nr_regex_substrings_destroy(&matches); + nr_php_zval_free(®ion_zval); + nr_php_zval_free(&qualifier_zval); +} + char* nr_lib_aws_sdk_php_get_command_arg_value(char* command_arg_name, NR_EXECUTE_PROTO) { zval* param_array = NULL; @@ -383,6 +551,10 @@ NR_PHP_WRAPPER(nr_aws_client_call) { nr_lib_aws_sdk_php_sqs_handle(auto_segment, command_name_string, Z_STRLEN_P(command_name), NR_EXECUTE_ORIG_ARGS); + } else if (AWS_CLASS_IS("Aws\\Lambda\\LambdaClient", "LambdaClient")) { + nr_lib_aws_sdk_php_lambda_handle(auto_segment, command_name_string, + Z_STRLEN_P(command_name), + NR_EXECUTE_ORIG_ARGS); } #undef AWS_CLASS_IS @@ -566,5 +738,6 @@ void nr_aws_sdk_php_enable() { nr_php_wrap_user_function_before_after_clean( NR_PSTR("Aws\\AwsClient::__call"), NULL, nr_aws_client_call, nr_aws_client_call); + #endif } diff --git a/agent/lib_aws_sdk_php.h b/agent/lib_aws_sdk_php.h index 6eb0c1e54..f9798df28 100644 --- a/agent/lib_aws_sdk_php.h +++ b/agent/lib_aws_sdk_php.h @@ -74,6 +74,18 @@ extern void nr_lib_aws_sdk_php_sqs_handle(nr_segment_t* segment, size_t command_name_len, NR_EXECUTE_PROTO); +/* + * Purpose : Handle when a LambdaClient::invoke command happens + * + * Params : 1. NR_EXECUTE_ORIG_ARGS (execute_data, func_return_value) + * 2. cloud_attrs : the cloud attributes pointer to be + * populated with the ARN + * + * Returns : + * + */ +void nr_aws_lambda_invoke(NR_EXECUTE_PROTO, nr_segment_cloud_attrs_t* cloud_attrs); + /* * Purpose : The second argument to the Aws/AwsClient::__call function should be * an array, the first element of which is itself an array of arguments that diff --git a/agent/php_newrelic.h b/agent/php_newrelic.h index 6afaf531a..6b59d5e34 100644 --- a/agent/php_newrelic.h +++ b/agent/php_newrelic.h @@ -328,7 +328,6 @@ bool wordpress_core; /* set based on nrinistr_t wordpress_hooks_skip_filename; /* newrelic.framework.wordpress.hooks_skip_filename */ - nrinibool_t analytics_events_enabled; /* DEPRECATED newrelic.analytics_events.enabled */ nrinibool_t @@ -383,6 +382,11 @@ nrinibool_t nrinibool_t database_name_reporting_enabled; /* newrelic.datastore_tracer.database_name_reporting.enabled */ +/* + * Cloud relationship settings + */ +nrinistr_t + aws_account_id; /* newrelic.cloud.aws.account_id */ /* * Deprecated settings that control request parameter capture. @@ -464,6 +468,7 @@ nr_stack_t wordpress_tag_states; /* stack of bools indicating bool check_cufa; /* Whether we need to check cufa because we are instrumenting hooks, or whether we can skip cufa */ char* wordpress_tag; /* The current WordPress tag */ + #endif //OAPI nr_matcher_t* wordpress_plugin_matcher; /* Matcher for plugin filenames */ @@ -481,6 +486,8 @@ int php_cur_stack_depth; /* Total current depth of PHP stack, measured in PHP nrphpcufafn_t cufa_callback; /* The current call_user_func_array callback, if any */ + +nr_regex_t* aws_arn_regex; /* The compiled regex to search for ARNs */ /* * We instrument database connection constructors and store the instance * information in a hash keyed by a string containing the connection resource diff --git a/agent/php_nrini.c b/agent/php_nrini.c index 4a2f7c471..a02948d6f 100644 --- a/agent/php_nrini.c +++ b/agent/php_nrini.c @@ -3100,6 +3100,18 @@ STD_PHP_INI_ENTRY_EX("newrelic.vulnerability_management.composer_api.enabled", newrelic_globals, nr_enabled_disabled_dh) +/* + * Cloud relationship settings + */ +STD_PHP_INI_ENTRY_EX("newrelic.cloud.aws.account_id", + "1", + NR_PHP_REQUEST, + nr_string_mh, + aws_account_id, + zend_newrelic_globals, + newrelic_globals, + 0) + /* * Messaging API */ diff --git a/axiom/nr_segment.h b/axiom/nr_segment.h index 646f11e5f..1e9cce8ec 100644 --- a/axiom/nr_segment.h +++ b/axiom/nr_segment.h @@ -159,6 +159,8 @@ typedef struct _nr_segment_cloud_attrs_t { relationship.*/ char* cloud_account_id; /*The cloud provider account ID. Needed for SQS relationship.*/ + char* cloud_platform; /*The platform hosting the cloud. Needed for Lambda + relationship.*/ char* cloud_resource_id; /*Unique cloud provider identifier. For AWS, this is the ARN of the AWS resource being accessed.*/ char* aws_operation; /*AWS specific operation name.*/ diff --git a/axiom/nr_segment_traces.c b/axiom/nr_segment_traces.c index d2f1fddac..6d8870565 100644 --- a/axiom/nr_segment_traces.c +++ b/axiom/nr_segment_traces.c @@ -658,4 +658,9 @@ extern void nr_segment_traces_add_cloud_attributes( segment->attributes, NR_CLOUD_AGENT_ATTRIBUTE_DESTINATION, NR_ATTR_AWS_OPERATION, cloud_attrs->aws_operation); } + if (!nr_strempty(cloud_attrs->cloud_platform)) { + nr_attributes_agent_add_string( + segment->attributes, NR_CLOUD_AGENT_ATTRIBUTE_DESTINATION, + NR_ATTR_CLOUD_PLATFORM, cloud_attrs->cloud_platform); + } } diff --git a/axiom/nr_span_event.h b/axiom/nr_span_event.h index 33dced5c2..54d52e025 100644 --- a/axiom/nr_span_event.h +++ b/axiom/nr_span_event.h @@ -24,6 +24,7 @@ #define NR_ATTR_CLOUD_REGION "cloud.region" #define NR_ATTR_CLOUD_ACCOUNT_ID "cloud.account.id" #define NR_ATTR_CLOUD_RESOURCE_ID "cloud.resource_id" +#define NR_ATTR_CLOUD_PLATFORM "cloud.platform" #define NR_ATTR_AWS_OPERATION "aws.operation" typedef struct _nr_span_event_t nr_span_event_t; From b106a2d389303b1b1949d200ec4036ee2456436d Mon Sep 17 00:00:00 2001 From: Zach Neumann Date: Thu, 13 Feb 2025 11:03:11 -0700 Subject: [PATCH 02/18] add account id ini --- agent/scripts/newrelic.ini.template | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/agent/scripts/newrelic.ini.template b/agent/scripts/newrelic.ini.template index b406628a9..82fef0862 100644 --- a/agent/scripts/newrelic.ini.template +++ b/agent/scripts/newrelic.ini.template @@ -1352,3 +1352,12 @@ newrelic.daemon.logfile = "/var/log/newrelic/newrelic-daemon.log" ; newrelic.span_events.attributes.include/exclude ; ;newrelic.message_tracer.segment_parameters.enabled = true + +; Setting: newrelic.cloud.aws.account_id +; Type : string +; Scope : per-directory +; Default: none +; Info : AWS account id that is used to map entities called +; via the AWS SDK. +; +;newrelic.cloud.aws.account_id = "" From f078838ebcfa3d59bb44aa04d53c2cc203ce6513 Mon Sep 17 00:00:00 2001 From: Zach Neumann Date: Thu, 13 Feb 2025 11:03:20 -0700 Subject: [PATCH 03/18] lambda unit tests --- agent/tests/test_lib_aws_sdk_php.c | 37 ++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/agent/tests/test_lib_aws_sdk_php.c b/agent/tests/test_lib_aws_sdk_php.c index 0ac040424..66f7f9c3a 100644 --- a/agent/tests/test_lib_aws_sdk_php.c +++ b/agent/tests/test_lib_aws_sdk_php.c @@ -61,6 +61,19 @@ NR_PHP_WRAPPER(expect_arg_value_null) { } NR_PHP_WRAPPER_END +NR_PHP_WRAPPER(aws_lambda_invoke_wrapper) { + nr_segment_cloud_attrs_t cloud_attrs = {0}; + zval* expected = nr_php_get_user_func_arg(1, NR_EXECUTE_ORIG_ARGS); + nr_aws_lambda_invoke(NR_EXECUTE_ORIG_ARGS, &cloud_attrs); + (void)wraprec; + + tlib_pass_if_str_equal("Expected should match reconstructed arn", + Z_STRVAL_P(expected), + cloud_attrs.cloud_resource_id); + NR_PHP_WRAPPER_CALL; +} +NR_PHP_WRAPPER_END + static void test_nr_lib_aws_sdk_php_get_command_arg_value() { zval* expr = NULL; zval* first_arg = NULL; @@ -458,6 +471,29 @@ static void test_nr_lib_aws_sdk_php_handle_version(void) { tlib_php_request_end(); } +static void test_nr_lib_aws_sdk_php_lambda_invoke() { + tlib_php_engine_create(""); + tlib_php_request_start(); + + tlib_php_request_eval("function lambda_invoke($a, $b) { return; }"); + nr_php_wrap_user_function(NR_PSTR("lambda_invoke"), aws_lambda_invoke_wrapper); + + char* args + = "array(" + " 0 => array(" + " 'FunctionName' => 'us-east-2:012345678901:function'" + " )" + ")"; + zval* array_arg = tlib_php_request_eval_expr(args); + char* expect = "'arn:aws:lamba:us-east-2:012345678901:function'"; + zval* expect_arg = tlib_php_request_eval_expr(expect); + zval* expr = nr_php_call(NULL, "lambda_invoke", expect_arg, array_arg); + tlib_pass_if_not_null("Expression should evaluate.", expr); + + tlib_php_request_end(); + tlib_php_engine_destroy(); +} + void test_main(void* p NRUNUSED) { tlib_php_engine_create(""); test_nr_lib_aws_sdk_php_add_supportability_service_metric(); @@ -466,6 +502,7 @@ void test_main(void* p NRUNUSED) { #if ZEND_MODULE_API_NO >= ZEND_8_1_X_API_NO test_nr_lib_aws_sdk_php_sqs_parse_queueurl(); test_nr_lib_aws_sdk_php_get_command_arg_value(); + test_nr_lib_aws_sdk_php_lambda_invoke(); #endif /* PHP 8.1+ */ } #else From ffd9ca2f291b1d4ef82baf479c42c6721e2765a6 Mon Sep 17 00:00:00 2001 From: Zach Neumann Date: Thu, 13 Feb 2025 11:43:41 -0700 Subject: [PATCH 04/18] fix --- agent/lib_aws_sdk_php.c | 49 +++++++++------ agent/lib_aws_sdk_php.h | 5 ++ agent/php_minit.c | 1 - agent/php_mshutdown.c | 4 ++ agent/tests/test_lib_aws_sdk_php.c | 96 ++++++++++++++++++++++++++++-- 5 files changed, 131 insertions(+), 24 deletions(-) diff --git a/agent/lib_aws_sdk_php.c b/agent/lib_aws_sdk_php.c index 285d669f6..e95f4a869 100644 --- a/agent/lib_aws_sdk_php.c +++ b/agent/lib_aws_sdk_php.c @@ -321,7 +321,7 @@ void nr_lib_aws_sdk_php_lambda_handle(nr_segment_t* auto_segment, return; } - if (NULL == retval_ptr) { + if (NULL == *retval_ptr) { /* Do not instrument when an exception has happened */ return; } @@ -362,23 +362,38 @@ void nr_lib_aws_sdk_php_lambda_handle(nr_segment_t* auto_segment, nr_segment_traces_add_cloud_attributes(external_segment, &cloud_attrs); nr_segment_external_params_t external_params = {.library = "aws_sdk"}; zval* data = nr_php_get_zval_object_property(*retval_ptr, "data"); - if (NULL != data && IS_ARRAY == Z_TYPE_P(data)) { + if (nr_php_is_zval_valid_array(data)) { zval* status_code = nr_php_zend_hash_find(Z_ARRVAL_P(data), "StatusCode"); - if (NULL != status_code && IS_LONG == Z_TYPE_P(status_code)) { + if (nr_php_is_zval_valid_integer(status_code)) { external_params.status = Z_LVAL_P(status_code); } zval* metadata = nr_php_zend_hash_find(Z_ARRVAL_P(data), "@metadata"); - if (NULL != metadata && IS_REFERENCE == Z_TYPE_P(metadata) - && IS_ARRAY == Z_TYPE_P(Z_REFVAL_P(metadata))) { - zval* uri = nr_php_zend_hash_find(Z_ARRVAL_P(Z_REFVAL_P(metadata)), "effectiveUri"); - if (NULL != uri && IS_STRING == Z_TYPE_P(uri)) { - external_params.uri = Z_STRVAL_P(uri); + if (NULL != metadata && IS_REFERENCE == Z_TYPE_P(metadata)) { + metadata = Z_REFVAL_P(metadata); + if (IS_ARRAY == Z_TYPE_P(metadata)) { + zval* uri = nr_php_zend_hash_find(Z_ARRVAL_P(metadata), "effectiveUri"); + if (nr_php_is_zval_valid_string(uri)) { + external_params.uri = Z_STRVAL_P(uri); + } } } } nr_segment_external_end(&auto_segment, &external_params); } +/* This stores the compiled regex to parse AWS ARNs. The compilation happens when + * it is first needed and is destroyed in mshutdown + */ +static nr_regex_t* aws_arn_regex; + +static void nr_aws_sdk_compile_regex(void) { + aws_arn_regex = nr_regex_create(AWS_ARN_REGEX, 0, 0); +} + +void nr_aws_sdk_mshutdown(void) { + nr_regex_destroy(&aws_arn_regex); +} + void nr_aws_lambda_invoke(NR_EXECUTE_PROTO, nr_segment_cloud_attrs_t* cloud_attrs) { zval* call_args = nr_php_get_user_func_arg(2, NR_EXECUTE_ORIG_ARGS); zval* this_obj = NR_PHP_USER_FN_THIS(); @@ -391,26 +406,26 @@ void nr_aws_lambda_invoke(NR_EXECUTE_PROTO, nr_segment_cloud_attrs_t* cloud_attr char* accountID = NULL; /* verify arguments */ - if (NULL == call_args || IS_ARRAY != Z_TYPE_P(call_args)) { + if (!nr_php_is_zval_valid_array(call_args)) { return; } zval* lambda_args = nr_php_zend_hash_index_find(Z_ARRVAL_P(call_args), 0); - if (NULL == lambda_args || IS_ARRAY != Z_TYPE_P(lambda_args)) { + if (!nr_php_is_zval_valid_array(lambda_args)) { return; } zval* lambda_name = nr_php_zend_hash_find(Z_ARRVAL_P(lambda_args), "FunctionName"); - if (NULL == lambda_name || IS_STRING != Z_TYPE_P(lambda_name)) { + if (!nr_php_is_zval_valid_string(lambda_name)) { return; } - /* Compile the regex */ - if (NULL == NRPRG(aws_arn_regex)) { - NRPRG(aws_arn_regex) = nr_regex_create(AWS_ARN_REGEX, 0, 0); + /* Ensure regex exists */ + if (NULL == aws_arn_regex) { + nr_aws_sdk_compile_regex(); } /* Extract all information possible from the passed lambda name via regex */ nr_regex_substrings_t* matches = - nr_regex_match_capture(NRPRG(aws_arn_regex), + nr_regex_match_capture(aws_arn_regex, Z_STRVAL_P(lambda_name), Z_STRLEN_P(lambda_name)); function_name = nr_regex_substrings_get_named(matches, "functionName"); @@ -429,10 +444,10 @@ void nr_aws_lambda_invoke(NR_EXECUTE_PROTO, nr_segment_cloud_attrs_t* cloud_attr } if (nr_strempty(accountID)) { accountID = NRINI(aws_account_id); - accountID = "012345"; } if (nr_strempty(region)) { - region_zval = nr_php_call(this_obj, "getRegion"); + region_zval + = nr_php_get_zval_object_property(this_obj, "region"); if (nr_php_is_zval_valid_string(region_zval)) { region = Z_STRVAL_P(region_zval); } diff --git a/agent/lib_aws_sdk_php.h b/agent/lib_aws_sdk_php.h index f9798df28..1a0ddf237 100644 --- a/agent/lib_aws_sdk_php.h +++ b/agent/lib_aws_sdk_php.h @@ -86,6 +86,11 @@ extern void nr_lib_aws_sdk_php_sqs_handle(nr_segment_t* segment, */ void nr_aws_lambda_invoke(NR_EXECUTE_PROTO, nr_segment_cloud_attrs_t* cloud_attrs); +/* + * Purpose : Handles regex destruction during mshutdown + */ +void nr_aws_sdk_mshutdown(void); + /* * Purpose : The second argument to the Aws/AwsClient::__call function should be * an array, the first element of which is itself an array of arguments that diff --git a/agent/php_minit.c b/agent/php_minit.c index 7cf4f3e8e..d16b41c28 100644 --- a/agent/php_minit.c +++ b/agent/php_minit.c @@ -716,7 +716,6 @@ PHP_MINIT_FUNCTION(newrelic) { nr_guzzle4_minit(TSRMLS_C); nr_guzzle6_minit(TSRMLS_C); nr_laravel_minit(TSRMLS_C); - nr_wordpress_minit(); nr_php_set_opcode_handlers(); nrl_debug(NRL_INIT, "MINIT processing done"); diff --git a/agent/php_mshutdown.c b/agent/php_mshutdown.c index 1c67d38b4..f3ac15b16 100644 --- a/agent/php_mshutdown.c +++ b/agent/php_mshutdown.c @@ -42,6 +42,10 @@ PHP_MSHUTDOWN_FUNCTION(newrelic) { nr_wordpress_mshutdown(); +#if ZEND_MODULE_API_NO >= ZEND_8_1_X_API_NO /* PHP 8.1+ */ + nr_aws_sdk_mshutdown(); +#endif + /* restore header handler */ sapi_module.header_handler = NR_PHP_PROCESS_GLOBALS(orig_header_handler); NR_PHP_PROCESS_GLOBALS(orig_header_handler) = NULL; diff --git a/agent/tests/test_lib_aws_sdk_php.c b/agent/tests/test_lib_aws_sdk_php.c index 66f7f9c3a..a071e3f71 100644 --- a/agent/tests/test_lib_aws_sdk_php.c +++ b/agent/tests/test_lib_aws_sdk_php.c @@ -63,13 +63,23 @@ NR_PHP_WRAPPER_END NR_PHP_WRAPPER(aws_lambda_invoke_wrapper) { nr_segment_cloud_attrs_t cloud_attrs = {0}; + /* + * Because argument 1 is not used in instrumentation, we will use it + * to pass in the expected value + */ zval* expected = nr_php_get_user_func_arg(1, NR_EXECUTE_ORIG_ARGS); nr_aws_lambda_invoke(NR_EXECUTE_ORIG_ARGS, &cloud_attrs); (void)wraprec; - tlib_pass_if_str_equal("Expected should match reconstructed arn", - Z_STRVAL_P(expected), - cloud_attrs.cloud_resource_id); + if (nr_php_is_zval_valid_string(expected)) { + tlib_pass_if_str_equal("Expected should match reconstructed arn", + Z_STRVAL_P(expected), + cloud_attrs.cloud_resource_id); + } else { + tlib_pass_if_str_equal("Expected should match reconstructed arn", + NULL, + cloud_attrs.cloud_resource_id); + } NR_PHP_WRAPPER_CALL; } NR_PHP_WRAPPER_END @@ -471,28 +481,102 @@ static void test_nr_lib_aws_sdk_php_handle_version(void) { tlib_php_request_end(); } +#if ZEND_MODULE_API_NO >= ZEND_8_1_X_API_NO static void test_nr_lib_aws_sdk_php_lambda_invoke() { - tlib_php_engine_create(""); + tlib_php_engine_create("newrelic.cloud.aws.account_id=\"test_account\""); tlib_php_request_start(); tlib_php_request_eval("function lambda_invoke($a, $b) { return; }"); nr_php_wrap_user_function(NR_PSTR("lambda_invoke"), aws_lambda_invoke_wrapper); + /* Test full-info run */ char* args = "array(" " 0 => array(" - " 'FunctionName' => 'us-east-2:012345678901:function'" + " 'FunctionName' => 'us-east-2:012345678901:function:my-function'" " )" ")"; zval* array_arg = tlib_php_request_eval_expr(args); - char* expect = "'arn:aws:lamba:us-east-2:012345678901:function'"; + char* expect = "'arn:aws:lambda:us-east-2:012345678901:function:my-function'"; zval* expect_arg = tlib_php_request_eval_expr(expect); zval* expr = nr_php_call(NULL, "lambda_invoke", expect_arg, array_arg); tlib_pass_if_not_null("Expression should evaluate.", expr); + nr_php_zval_free(&expr); + nr_php_zval_free(&expect_arg); + nr_php_zval_free(&array_arg); + + /* Test alias full-info run */ + args + = "array(" + " 0 => array(" + " 'FunctionName' => 'us-east-2:012345678901:function:my-function:v1'" + " )" + ")"; + array_arg = tlib_php_request_eval_expr(args); + expect = "'arn:aws:lambda:us-east-2:012345678901:function:my-function:v1'"; + expect_arg = tlib_php_request_eval_expr(expect); + expr = nr_php_call(NULL, "lambda_invoke", expect_arg, array_arg); + tlib_pass_if_not_null("Expression should evaluate.", expr); + nr_php_zval_free(&expr); + nr_php_zval_free(&expect_arg); + nr_php_zval_free(&array_arg); + + /* Test INI extract */ + args + = "array(" + " 0 => array(" + " 'FunctionName' => 'us-east-2:my-function'" + " )" + ")"; + array_arg = tlib_php_request_eval_expr(args); + expect = "'arn:aws:lambda:us-east-2:test_account:function:my-function'"; + expect_arg = tlib_php_request_eval_expr(expect); + expr = nr_php_call(NULL, "lambda_invoke", expect_arg, array_arg); + tlib_pass_if_not_null("Expression should evaluate.", expr); + nr_php_zval_free(&expr); + nr_php_zval_free(&expect_arg); + nr_php_zval_free(&array_arg); + + /* Test invalid arg 1 */ + args + = "array(" + " 0 => array(" + " 'FunctionName' => 123" + " )" + ")"; + array_arg = tlib_php_request_eval_expr(args); + expect = "NULL"; + expect_arg = tlib_php_request_eval_expr(expect); + expr = nr_php_call(NULL, "lambda_invoke", expect_arg, array_arg); + tlib_pass_if_not_null("Expression should evaluate.", expr); + nr_php_zval_free(&expr); + nr_php_zval_free(&array_arg); + + /* Test invalid arg 2 */ + args + = "array(" + " 0 => array(" + " )" + ")"; + array_arg = tlib_php_request_eval_expr(args); + expr = nr_php_call(NULL, "lambda_invoke", expect_arg, array_arg); + tlib_pass_if_not_null("Expression should evaluate.", expr); + nr_php_zval_free(&expr); + nr_php_zval_free(&array_arg); + + /* Test invalid arg 3 */ + args = "array()"; + array_arg = tlib_php_request_eval_expr(args); + expr = nr_php_call(NULL, "lambda_invoke", expect_arg, array_arg); + tlib_pass_if_not_null("Expression should evaluate.", expr); + nr_php_zval_free(&expr); + nr_php_zval_free(&expect_arg); + nr_php_zval_free(&array_arg); tlib_php_request_end(); tlib_php_engine_destroy(); } +#endif /* PHP 8.1+ */ void test_main(void* p NRUNUSED) { tlib_php_engine_create(""); From e6143db9cc67b485879184a3555e85b36f9fb4b3 Mon Sep 17 00:00:00 2001 From: Zach Neumann Date: Tue, 25 Feb 2025 14:42:01 -0700 Subject: [PATCH 05/18] ini description --- agent/scripts/newrelic.ini.template | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/agent/scripts/newrelic.ini.template b/agent/scripts/newrelic.ini.template index 82fef0862..2ea1d71bb 100644 --- a/agent/scripts/newrelic.ini.template +++ b/agent/scripts/newrelic.ini.template @@ -1357,7 +1357,11 @@ newrelic.daemon.logfile = "/var/log/newrelic/newrelic-daemon.log" ; Type : string ; Scope : per-directory ; Default: none -; Info : AWS account id that is used to map entities called -; via the AWS SDK. +; Info : This setting is read by some cloud service instrumentation so the +; cloud.resource_id attribute can be set in the respective spans. +; +; AWS DynamoDB and Kinesis are services that require this value to be +; able to populate the cloud.resource_id attribute. Likewise, AWS Lambda +; requires that this value when the account ID is not part of the function name. ; ;newrelic.cloud.aws.account_id = "" From 18425ad599189f1db4006026231c8410f8963010 Mon Sep 17 00:00:00 2001 From: Zach Neumann Date: Tue, 25 Feb 2025 14:59:43 -0700 Subject: [PATCH 06/18] fix --- agent/lib_aws_sdk_php.c | 17 ++++++++++++++++- agent/php_minit.c | 1 + agent/tests/test_lib_aws_sdk_php.c | 1 + 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/agent/lib_aws_sdk_php.c b/agent/lib_aws_sdk_php.c index e95f4a869..6ef0f5219 100644 --- a/agent/lib_aws_sdk_php.c +++ b/agent/lib_aws_sdk_php.c @@ -340,7 +340,7 @@ void nr_lib_aws_sdk_php_lambda_handle(nr_segment_t* auto_segment, /* reconstruct the ARN */ nr_aws_lambda_invoke(NR_EXECUTE_ORIG_ARGS, &cloud_attrs); - if (!cloud_attrs.cloud_resource_id) { + if (NULL == cloud_attrs.cloud_resource_id) { /* we do not want to instrument if we cannot reconstruct the ARN */ return; } @@ -352,6 +352,7 @@ void nr_lib_aws_sdk_php_lambda_handle(nr_segment_t* auto_segment, */ external_segment = nr_segment_start(NRPRG(txn), NULL, NULL); if (NULL == external_segment) { + nr_free(cloud_attrs.cloud_resource_id); return; } /* re-use start time from auto_segment started in func_begin */ @@ -379,6 +380,7 @@ void nr_lib_aws_sdk_php_lambda_handle(nr_segment_t* auto_segment, } } nr_segment_external_end(&auto_segment, &external_params); + nr_free(cloud_attrs.cloud_resource_id); } /* This stores the compiled regex to parse AWS ARNs. The compilation happens when @@ -404,6 +406,7 @@ void nr_aws_lambda_invoke(NR_EXECUTE_PROTO, nr_segment_cloud_attrs_t* cloud_attr char* qualifier = NULL; zval* qualifier_zval = NULL; char* accountID = NULL; + bool using_account_id_ini = false; /* verify arguments */ if (!nr_php_is_zval_valid_array(call_args)) { @@ -439,11 +442,17 @@ void nr_aws_lambda_invoke(NR_EXECUTE_PROTO, nr_segment_cloud_attrs_t* cloud_attr * Cannot get the needed data. Function name is required in the * argument, so this won't happen in normal operation */ + nr_free(function_name); + nr_free(accountID); + nr_free(region); + nr_free(qualifier); nr_regex_substrings_destroy(&matches); return; } if (nr_strempty(accountID)) { + nr_free(accountID); accountID = NRINI(aws_account_id); + using_account_id_ini = true; } if (nr_strempty(region)) { region_zval @@ -474,6 +483,12 @@ void nr_aws_lambda_invoke(NR_EXECUTE_PROTO, nr_segment_cloud_attrs_t* cloud_attr } nr_regex_substrings_destroy(&matches); + nr_free(function_name); + if (!using_account_id_ini) { + nr_free(accountID); + } + nr_free(region); + nr_free(qualifier); nr_php_zval_free(®ion_zval); nr_php_zval_free(&qualifier_zval); } diff --git a/agent/php_minit.c b/agent/php_minit.c index d16b41c28..7cf4f3e8e 100644 --- a/agent/php_minit.c +++ b/agent/php_minit.c @@ -716,6 +716,7 @@ PHP_MINIT_FUNCTION(newrelic) { nr_guzzle4_minit(TSRMLS_C); nr_guzzle6_minit(TSRMLS_C); nr_laravel_minit(TSRMLS_C); + nr_wordpress_minit(); nr_php_set_opcode_handlers(); nrl_debug(NRL_INIT, "MINIT processing done"); diff --git a/agent/tests/test_lib_aws_sdk_php.c b/agent/tests/test_lib_aws_sdk_php.c index a071e3f71..c09035775 100644 --- a/agent/tests/test_lib_aws_sdk_php.c +++ b/agent/tests/test_lib_aws_sdk_php.c @@ -81,6 +81,7 @@ NR_PHP_WRAPPER(aws_lambda_invoke_wrapper) { cloud_attrs.cloud_resource_id); } NR_PHP_WRAPPER_CALL; + nr_free(cloud_attrs.cloud_resource_id); } NR_PHP_WRAPPER_END From 338cfb63a7ec6245c971f8669681c12783f8cca8 Mon Sep 17 00:00:00 2001 From: Zach Neumann Date: Wed, 26 Feb 2025 11:42:36 -0700 Subject: [PATCH 07/18] remove getqualifier --- agent/lib_aws_sdk_php.c | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/agent/lib_aws_sdk_php.c b/agent/lib_aws_sdk_php.c index 6ef0f5219..5694443a7 100644 --- a/agent/lib_aws_sdk_php.c +++ b/agent/lib_aws_sdk_php.c @@ -404,7 +404,6 @@ void nr_aws_lambda_invoke(NR_EXECUTE_PROTO, nr_segment_cloud_attrs_t* cloud_attr char* region = NULL; zval* region_zval = NULL; char* qualifier = NULL; - zval* qualifier_zval = NULL; char* accountID = NULL; bool using_account_id_ini = false; @@ -461,16 +460,10 @@ void nr_aws_lambda_invoke(NR_EXECUTE_PROTO, nr_segment_cloud_attrs_t* cloud_attr region = Z_STRVAL_P(region_zval); } } - if (nr_strempty(qualifier)) { - qualifier_zval = NULL;//nr_php_call(this_obj, "getQualifier"); - if (nr_php_is_zval_valid_string(qualifier_zval)) { - qualifier = Z_STRVAL_P(qualifier_zval); - } - } if (!nr_strempty(accountID) && !nr_strempty(region)) { // construct the ARN - if (qualifier) { + if (!nr_strempty(qualifier)) { arn = nr_formatf("arn:aws:lambda:%s:%s:function:%s:%s", region, accountID, function_name, qualifier); } else { @@ -490,7 +483,6 @@ void nr_aws_lambda_invoke(NR_EXECUTE_PROTO, nr_segment_cloud_attrs_t* cloud_attr nr_free(region); nr_free(qualifier); nr_php_zval_free(®ion_zval); - nr_php_zval_free(&qualifier_zval); } char* nr_lib_aws_sdk_php_get_command_arg_value(char* command_arg_name, From 0b7efecd9fc73afea69a8323c3317ec32af583ed Mon Sep 17 00:00:00 2001 From: Zach Neumann Date: Wed, 26 Feb 2025 11:51:35 -0700 Subject: [PATCH 08/18] always instrument --- agent/lib_aws_sdk_php.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/agent/lib_aws_sdk_php.c b/agent/lib_aws_sdk_php.c index 5694443a7..525a2c2eb 100644 --- a/agent/lib_aws_sdk_php.c +++ b/agent/lib_aws_sdk_php.c @@ -340,10 +340,6 @@ void nr_lib_aws_sdk_php_lambda_handle(nr_segment_t* auto_segment, /* reconstruct the ARN */ nr_aws_lambda_invoke(NR_EXECUTE_ORIG_ARGS, &cloud_attrs); - if (NULL == cloud_attrs.cloud_resource_id) { - /* we do not want to instrument if we cannot reconstruct the ARN */ - return; - } /* * By this point, it's been determined that this call will be instrumented so From 5c45a0ef346dbb2bf7e1b28a8ed0de4cbabcbb3f Mon Sep 17 00:00:00 2001 From: Zach Neumann Date: Mon, 3 Mar 2025 10:23:36 -0700 Subject: [PATCH 09/18] PR fixes --- agent/lib_aws_sdk_php.c | 49 +++++++++++++++----------- agent/lib_aws_sdk_php.h | 3 +- agent/php_nrini.c | 31 +++++++++++++++-- agent/scripts/newrelic.ini.template | 1 + agent/tests/test_lib_aws_sdk_php.c | 53 ++++++++++++++++++++++++++--- 5 files changed, 109 insertions(+), 28 deletions(-) diff --git a/agent/lib_aws_sdk_php.c b/agent/lib_aws_sdk_php.c index 525a2c2eb..e100711e7 100644 --- a/agent/lib_aws_sdk_php.c +++ b/agent/lib_aws_sdk_php.c @@ -19,7 +19,7 @@ #include "lib_aws_sdk_php.h" #define PHP_PACKAGE_NAME "aws/aws-sdk-php" -#define AWS_ARN_REGEX "(arn:(aws[a-zA-Z-]*)?:lambda:)?" \ +#define AWS_LAMBDA_ARN_REGEX "(arn:(aws[a-zA-Z-]*)?:lambda:)?" \ "((?[a-z]{2}((-gov)|(-iso([a-z]?)))?-[a-z]+-\\d{1}):)?" \ "((?\\d{12}):)?" \ "(function:)?" \ @@ -331,16 +331,13 @@ void nr_lib_aws_sdk_php_lambda_handle(nr_segment_t* auto_segment, /* Determine if we instrument this command. */ if (AWS_COMMAND_IS("invoke")) { - // Command can be saved here if in the future - // we instrument more than 1 lambda command + /* reconstruct the ARN */ + nr_aws_sdk_lambda_client_invoke_parse_args(NR_EXECUTE_ORIG_ARGS, &cloud_attrs); } else { return; } #undef AWS_COMMAND_IS - /* reconstruct the ARN */ - nr_aws_lambda_invoke(NR_EXECUTE_ORIG_ARGS, &cloud_attrs); - /* * By this point, it's been determined that this call will be instrumented so * only create the segment now, grab the parent segment start time, add our @@ -367,15 +364,16 @@ void nr_lib_aws_sdk_php_lambda_handle(nr_segment_t* auto_segment, zval* metadata = nr_php_zend_hash_find(Z_ARRVAL_P(data), "@metadata"); if (NULL != metadata && IS_REFERENCE == Z_TYPE_P(metadata)) { metadata = Z_REFVAL_P(metadata); - if (IS_ARRAY == Z_TYPE_P(metadata)) { - zval* uri = nr_php_zend_hash_find(Z_ARRVAL_P(metadata), "effectiveUri"); - if (nr_php_is_zval_valid_string(uri)) { - external_params.uri = Z_STRVAL_P(uri); - } + } + if (nr_php_is_zval_valid_array(metadata)) { + zval* uri = nr_php_zend_hash_find(Z_ARRVAL_P(metadata), "effectiveUri"); + if (nr_php_is_zval_valid_string(uri)) { + external_params.uri = Z_STRVAL_P(uri); } } + } - nr_segment_external_end(&auto_segment, &external_params); + nr_segment_external_end(&external_segment, &external_params); nr_free(cloud_attrs.cloud_resource_id); } @@ -385,16 +383,16 @@ void nr_lib_aws_sdk_php_lambda_handle(nr_segment_t* auto_segment, static nr_regex_t* aws_arn_regex; static void nr_aws_sdk_compile_regex(void) { - aws_arn_regex = nr_regex_create(AWS_ARN_REGEX, 0, 0); + aws_arn_regex = nr_regex_create(AWS_LAMBDA_ARN_REGEX, 0, 0); } void nr_aws_sdk_mshutdown(void) { nr_regex_destroy(&aws_arn_regex); } -void nr_aws_lambda_invoke(NR_EXECUTE_PROTO, nr_segment_cloud_attrs_t* cloud_attrs) { +void nr_aws_sdk_lambda_client_invoke_parse_args(NR_EXECUTE_PROTO, nr_segment_cloud_attrs_t* cloud_attrs) { zval* call_args = nr_php_get_user_func_arg(2, NR_EXECUTE_ORIG_ARGS); - zval* this_obj = NR_PHP_USER_FN_THIS(); + zval* this_obj = getThis();//NR_PHP_USER_FN_THIS(); char* arn = NULL; char* function_name = NULL; char* region = NULL; @@ -450,15 +448,24 @@ void nr_aws_lambda_invoke(NR_EXECUTE_PROTO, nr_segment_cloud_attrs_t* cloud_attr using_account_id_ini = true; } if (nr_strempty(region)) { + zend_class_entry* base_class = NULL; + if (NULL != execute_data->func && NULL!= execute_data->func->common.scope) { + base_class = execute_data->func->common.scope; + } region_zval - = nr_php_get_zval_object_property(this_obj, "region"); + = nr_php_get_zval_object_property_with_class(this_obj, base_class, "region"); if (nr_php_is_zval_valid_string(region_zval)) { + /* + * In this case, region is likely to be NULL, but could be an empty + * string instead, so we must free + */ + nr_free(region); region = Z_STRVAL_P(region_zval); } } if (!nr_strempty(accountID) && !nr_strempty(region)) { - // construct the ARN + /* construct the ARN */ if (!nr_strempty(qualifier)) { arn = nr_formatf("arn:aws:lambda:%s:%s:function:%s:%s", region, accountID, function_name, qualifier); @@ -467,7 +474,7 @@ void nr_aws_lambda_invoke(NR_EXECUTE_PROTO, nr_segment_cloud_attrs_t* cloud_attr region, accountID, function_name); } - // Attatch the ARN + /* Attatch the ARN */ cloud_attrs->cloud_resource_id = arn; } @@ -476,9 +483,11 @@ void nr_aws_lambda_invoke(NR_EXECUTE_PROTO, nr_segment_cloud_attrs_t* cloud_attr if (!using_account_id_ini) { nr_free(accountID); } - nr_free(region); + /* if region_zval is a valid string, we have already freed region */ + if (!nr_php_is_zval_valid_string(region_zval)) { + nr_free(region); + } nr_free(qualifier); - nr_php_zval_free(®ion_zval); } char* nr_lib_aws_sdk_php_get_command_arg_value(char* command_arg_name, diff --git a/agent/lib_aws_sdk_php.h b/agent/lib_aws_sdk_php.h index 1a0ddf237..21bf0447b 100644 --- a/agent/lib_aws_sdk_php.h +++ b/agent/lib_aws_sdk_php.h @@ -83,8 +83,9 @@ extern void nr_lib_aws_sdk_php_sqs_handle(nr_segment_t* segment, * * Returns : * + * Note: The caller is responsible for freeing cloud_atters->cloud_resource_id */ -void nr_aws_lambda_invoke(NR_EXECUTE_PROTO, nr_segment_cloud_attrs_t* cloud_attrs); +void nr_aws_sdk_lambda_client_invoke_parse_args(NR_EXECUTE_PROTO, nr_segment_cloud_attrs_t* cloud_attrs); /* * Purpose : Handles regex destruction during mshutdown diff --git a/agent/php_nrini.c b/agent/php_nrini.c index a02948d6f..5c8c17d10 100644 --- a/agent/php_nrini.c +++ b/agent/php_nrini.c @@ -1212,6 +1212,33 @@ static PHP_INI_MH(nr_string_mh) { return FAILURE; } +static PHP_INI_MH(nr_aws_account_id_mh) { + nrinistr_t* p; + const int AWS_ACCOUNT_ID_SIZE = 12; + +#ifndef ZTS + char* base = (char*)mh_arg2; +#else + char* base = (char*)ts_resource(*((int*)mh_arg2)); +#endif + + p = (nrinistr_t*)(base + (size_t)mh_arg1); + + (void)entry; + (void)mh_arg3; + NR_UNUSED_TSRMLS; + + p->where = 0; + + if (NEW_VALUE_LEN == AWS_ACCOUNT_ID_SIZE) { + p->value = NEW_VALUE; + p->where = stage; + return SUCCESS; + } + + return FAILURE; +} + static PHP_INI_MH(nr_boolean_mh) { nrinibool_t* p; int val = 0; @@ -3104,9 +3131,9 @@ STD_PHP_INI_ENTRY_EX("newrelic.vulnerability_management.composer_api.enabled", * Cloud relationship settings */ STD_PHP_INI_ENTRY_EX("newrelic.cloud.aws.account_id", - "1", + "", NR_PHP_REQUEST, - nr_string_mh, + nr_aws_account_id_mh, aws_account_id, zend_newrelic_globals, newrelic_globals, diff --git a/agent/scripts/newrelic.ini.template b/agent/scripts/newrelic.ini.template index 2ea1d71bb..4bb9eb80e 100644 --- a/agent/scripts/newrelic.ini.template +++ b/agent/scripts/newrelic.ini.template @@ -1359,6 +1359,7 @@ newrelic.daemon.logfile = "/var/log/newrelic/newrelic-daemon.log" ; Default: none ; Info : This setting is read by some cloud service instrumentation so the ; cloud.resource_id attribute can be set in the respective spans. +; Do not include any "-" characters; this should be 12 characters. ; ; AWS DynamoDB and Kinesis are services that require this value to be ; able to populate the cloud.resource_id attribute. Likewise, AWS Lambda diff --git a/agent/tests/test_lib_aws_sdk_php.c b/agent/tests/test_lib_aws_sdk_php.c index c09035775..17df4285b 100644 --- a/agent/tests/test_lib_aws_sdk_php.c +++ b/agent/tests/test_lib_aws_sdk_php.c @@ -68,7 +68,7 @@ NR_PHP_WRAPPER(aws_lambda_invoke_wrapper) { * to pass in the expected value */ zval* expected = nr_php_get_user_func_arg(1, NR_EXECUTE_ORIG_ARGS); - nr_aws_lambda_invoke(NR_EXECUTE_ORIG_ARGS, &cloud_attrs); + nr_aws_sdk_lambda_client_invoke_parse_args(NR_EXECUTE_ORIG_ARGS, &cloud_attrs); (void)wraprec; if (nr_php_is_zval_valid_string(expected)) { @@ -484,12 +484,14 @@ static void test_nr_lib_aws_sdk_php_handle_version(void) { #if ZEND_MODULE_API_NO >= ZEND_8_1_X_API_NO static void test_nr_lib_aws_sdk_php_lambda_invoke() { - tlib_php_engine_create("newrelic.cloud.aws.account_id=\"test_account\""); + tlib_php_engine_create(""); tlib_php_request_start(); tlib_php_request_eval("function lambda_invoke($a, $b) { return; }"); nr_php_wrap_user_function(NR_PSTR("lambda_invoke"), aws_lambda_invoke_wrapper); + NRINI(aws_account_id) = "111122223333"; + /* Test full-info run */ char* args = "array(" @@ -530,7 +532,7 @@ static void test_nr_lib_aws_sdk_php_lambda_invoke() { " )" ")"; array_arg = tlib_php_request_eval_expr(args); - expect = "'arn:aws:lambda:us-east-2:test_account:function:my-function'"; + expect = "'arn:aws:lambda:us-east-2:111122223333:function:my-function'"; expect_arg = tlib_php_request_eval_expr(expect); expr = nr_php_call(NULL, "lambda_invoke", expect_arg, array_arg); tlib_pass_if_not_null("Expression should evaluate.", expr); @@ -538,11 +540,12 @@ static void test_nr_lib_aws_sdk_php_lambda_invoke() { nr_php_zval_free(&expect_arg); nr_php_zval_free(&array_arg); - /* Test invalid arg 1 */ + /* Test failed INI extract */ + NRINI(aws_account_id) = ""; args = "array(" " 0 => array(" - " 'FunctionName' => 123" + " 'FunctionName' => 'us-east-2:my-function'" " )" ")"; array_arg = tlib_php_request_eval_expr(args); @@ -552,6 +555,20 @@ static void test_nr_lib_aws_sdk_php_lambda_invoke() { tlib_pass_if_not_null("Expression should evaluate.", expr); nr_php_zval_free(&expr); nr_php_zval_free(&array_arg); + NRINI(aws_account_id) = "111122223333"; + + /* Test invalid arg 1 */ + args + = "array(" + " 0 => array(" + " 'FunctionName' => 123" + " )" + ")"; + array_arg = tlib_php_request_eval_expr(args); + expr = nr_php_call(NULL, "lambda_invoke", expect_arg, array_arg); + tlib_pass_if_not_null("Expression should evaluate.", expr); + nr_php_zval_free(&expr); + nr_php_zval_free(&array_arg); /* Test invalid arg 2 */ args @@ -571,6 +588,32 @@ static void test_nr_lib_aws_sdk_php_lambda_invoke() { expr = nr_php_call(NULL, "lambda_invoke", expect_arg, array_arg); tlib_pass_if_not_null("Expression should evaluate.", expr); nr_php_zval_free(&expr); + nr_php_zval_free(&array_arg); + + /* Test invalid arg 4 */ + args + = "array(" + " 0 => array(" + " 'FunctionName' => ''" + " )" + ")"; + array_arg = tlib_php_request_eval_expr(args); + expr = nr_php_call(NULL, "lambda_invoke", expect_arg, array_arg); + tlib_pass_if_not_null("Expression should evaluate.", expr); + nr_php_zval_free(&expr); + nr_php_zval_free(&array_arg); + + /* Test invalid arg 5 */ + args + = "array(" + " 0 => array(" + " 'FunctionName' => NULL" + " )" + ")"; + array_arg = tlib_php_request_eval_expr(args); + expr = nr_php_call(NULL, "lambda_invoke", expect_arg, array_arg); + tlib_pass_if_not_null("Expression should evaluate.", expr); + nr_php_zval_free(&expr); nr_php_zval_free(&expect_arg); nr_php_zval_free(&array_arg); From b78c3bd146a6c992c88f4180705ca9f08b2736df Mon Sep 17 00:00:00 2001 From: ZNeumann Date: Mon, 3 Mar 2025 10:28:54 -0700 Subject: [PATCH 10/18] Update agent/lib_aws_sdk_php.c Co-authored-by: Amber Sistla --- agent/lib_aws_sdk_php.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/lib_aws_sdk_php.c b/agent/lib_aws_sdk_php.c index e100711e7..61527ec20 100644 --- a/agent/lib_aws_sdk_php.c +++ b/agent/lib_aws_sdk_php.c @@ -429,7 +429,7 @@ void nr_aws_sdk_lambda_client_invoke_parse_args(NR_EXECUTE_PROTO, nr_segment_clo region = nr_regex_substrings_get_named(matches, "region"); qualifier = nr_regex_substrings_get_named(matches, "qualifier"); - /* suppliment missing information with API calls */ + /* supplement missing information with API calls */ if (nr_strempty(function_name)) { /* * Cannot get the needed data. Function name is required in the From 25475efa60752f455c8ca6bf90d5ef8e15ed1cc0 Mon Sep 17 00:00:00 2001 From: Zach Neumann Date: Mon, 3 Mar 2025 11:55:50 -0700 Subject: [PATCH 11/18] more unit tests --- agent/tests/test_lib_aws_sdk_php.c | 37 ++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/agent/tests/test_lib_aws_sdk_php.c b/agent/tests/test_lib_aws_sdk_php.c index 17df4285b..01bd01f5c 100644 --- a/agent/tests/test_lib_aws_sdk_php.c +++ b/agent/tests/test_lib_aws_sdk_php.c @@ -557,6 +557,23 @@ static void test_nr_lib_aws_sdk_php_lambda_invoke() { nr_php_zval_free(&array_arg); NRINI(aws_account_id) = "111122223333"; + /* Test NULL INI */ + NRINI(aws_account_id) = NULL; + args + = "array(" + " 0 => array(" + " 'FunctionName' => 'us-east-2:my-function'" + " )" + ")"; + array_arg = tlib_php_request_eval_expr(args); + expect = "NULL"; + expect_arg = tlib_php_request_eval_expr(expect); + expr = nr_php_call(NULL, "lambda_invoke", expect_arg, array_arg); + tlib_pass_if_not_null("Expression should evaluate.", expr); + nr_php_zval_free(&expr); + nr_php_zval_free(&array_arg); + NRINI(aws_account_id) = "111122223333"; + /* Test invalid arg 1 */ args = "array(" @@ -620,6 +637,25 @@ static void test_nr_lib_aws_sdk_php_lambda_invoke() { tlib_php_request_end(); tlib_php_engine_destroy(); } + +static void test_nr_lib_aws_sdk_ini() { + /* test too short */ + tlib_php_engine_create("newrelic.cloud.aws.account_id=\"12345678901\""); + tlib_php_request_start(); + tlib_pass_if_str_equal("Expected short account id to be dropped", + NULL, NRINI(aws_account_id)); + tlib_php_request_end(); + tlib_php_engine_destroy(); + + /* test too long */ + tlib_php_engine_create("newrelic.cloud.aws.account_id=\"1234567890123\""); + tlib_php_request_start(); + tlib_pass_if_str_equal("Expected short account id to be dropped", + NULL, NRINI(aws_account_id)); + tlib_php_request_end(); + tlib_php_engine_destroy(); +} + #endif /* PHP 8.1+ */ void test_main(void* p NRUNUSED) { @@ -627,6 +663,7 @@ void test_main(void* p NRUNUSED) { test_nr_lib_aws_sdk_php_add_supportability_service_metric(); test_nr_lib_aws_sdk_php_handle_version(); tlib_php_engine_destroy(); + test_nr_lib_aws_sdk_ini(); #if ZEND_MODULE_API_NO >= ZEND_8_1_X_API_NO test_nr_lib_aws_sdk_php_sqs_parse_queueurl(); test_nr_lib_aws_sdk_php_get_command_arg_value(); From 65b607aa0de7cfb4ce080788afdb63e8037fa058 Mon Sep 17 00:00:00 2001 From: Zach Neumann Date: Mon, 3 Mar 2025 12:19:04 -0700 Subject: [PATCH 12/18] fix --- agent/tests/test_lib_aws_sdk_php.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/agent/tests/test_lib_aws_sdk_php.c b/agent/tests/test_lib_aws_sdk_php.c index 01bd01f5c..0a55646f0 100644 --- a/agent/tests/test_lib_aws_sdk_php.c +++ b/agent/tests/test_lib_aws_sdk_php.c @@ -638,6 +638,8 @@ static void test_nr_lib_aws_sdk_php_lambda_invoke() { tlib_php_engine_destroy(); } +#endif /* PHP 8.1+ */ + static void test_nr_lib_aws_sdk_ini() { /* test too short */ tlib_php_engine_create("newrelic.cloud.aws.account_id=\"12345678901\""); @@ -656,8 +658,6 @@ static void test_nr_lib_aws_sdk_ini() { tlib_php_engine_destroy(); } -#endif /* PHP 8.1+ */ - void test_main(void* p NRUNUSED) { tlib_php_engine_create(""); test_nr_lib_aws_sdk_php_add_supportability_service_metric(); From d25f7731ec23e79a2b4cbd67aeec6df4afbeee13 Mon Sep 17 00:00:00 2001 From: Zach Neumann Date: Mon, 3 Mar 2025 12:35:40 -0700 Subject: [PATCH 13/18] fix test --- agent/tests/test_lib_aws_sdk_php.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/agent/tests/test_lib_aws_sdk_php.c b/agent/tests/test_lib_aws_sdk_php.c index 0a55646f0..83c6962a8 100644 --- a/agent/tests/test_lib_aws_sdk_php.c +++ b/agent/tests/test_lib_aws_sdk_php.c @@ -566,8 +566,6 @@ static void test_nr_lib_aws_sdk_php_lambda_invoke() { " )" ")"; array_arg = tlib_php_request_eval_expr(args); - expect = "NULL"; - expect_arg = tlib_php_request_eval_expr(expect); expr = nr_php_call(NULL, "lambda_invoke", expect_arg, array_arg); tlib_pass_if_not_null("Expression should evaluate.", expr); nr_php_zval_free(&expr); From b72ebc1104e0bf871dca57a2a458e488e1962505 Mon Sep 17 00:00:00 2001 From: Zach Neumann Date: Mon, 3 Mar 2025 14:22:05 -0700 Subject: [PATCH 14/18] check empty --- agent/lib_aws_sdk_php.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/lib_aws_sdk_php.c b/agent/lib_aws_sdk_php.c index 61527ec20..71fc5f60e 100644 --- a/agent/lib_aws_sdk_php.c +++ b/agent/lib_aws_sdk_php.c @@ -367,7 +367,7 @@ void nr_lib_aws_sdk_php_lambda_handle(nr_segment_t* auto_segment, } if (nr_php_is_zval_valid_array(metadata)) { zval* uri = nr_php_zend_hash_find(Z_ARRVAL_P(metadata), "effectiveUri"); - if (nr_php_is_zval_valid_string(uri)) { + if (nr_php_is_zval_valid_string(uri) && !nr_strempty(Z_STRVAL_P(uri))) { external_params.uri = Z_STRVAL_P(uri); } } From b4b8d4f2b42bde688df4b7cee6f15bd99d3391cc Mon Sep 17 00:00:00 2001 From: ZNeumann Date: Mon, 3 Mar 2025 14:22:46 -0700 Subject: [PATCH 15/18] Update agent/lib_aws_sdk_php.h Co-authored-by: Amber Sistla --- agent/lib_aws_sdk_php.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/lib_aws_sdk_php.h b/agent/lib_aws_sdk_php.h index 21bf0447b..1b52a4772 100644 --- a/agent/lib_aws_sdk_php.h +++ b/agent/lib_aws_sdk_php.h @@ -83,7 +83,7 @@ extern void nr_lib_aws_sdk_php_sqs_handle(nr_segment_t* segment, * * Returns : * - * Note: The caller is responsible for freeing cloud_atters->cloud_resource_id + * Note: The caller is responsible for freeing cloud_attrs->cloud_resource_id */ void nr_aws_sdk_lambda_client_invoke_parse_args(NR_EXECUTE_PROTO, nr_segment_cloud_attrs_t* cloud_attrs); From ad9005b131535eb45c84f05e1422738b16ee49d1 Mon Sep 17 00:00:00 2001 From: ZNeumann Date: Mon, 3 Mar 2025 14:22:56 -0700 Subject: [PATCH 16/18] Update agent/lib_aws_sdk_php.c Co-authored-by: Amber Sistla --- agent/lib_aws_sdk_php.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/lib_aws_sdk_php.c b/agent/lib_aws_sdk_php.c index 71fc5f60e..d7940c624 100644 --- a/agent/lib_aws_sdk_php.c +++ b/agent/lib_aws_sdk_php.c @@ -474,7 +474,7 @@ void nr_aws_sdk_lambda_client_invoke_parse_args(NR_EXECUTE_PROTO, nr_segment_clo region, accountID, function_name); } - /* Attatch the ARN */ + /* Attach the ARN */ cloud_attrs->cloud_resource_id = arn; } From 50aaa3c55da96aaa0b34449d87ec3623b33c908a Mon Sep 17 00:00:00 2001 From: Zach Neumann Date: Wed, 5 Mar 2025 08:13:32 -0700 Subject: [PATCH 17/18] minor changes --- agent/lib_aws_sdk_php.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/agent/lib_aws_sdk_php.c b/agent/lib_aws_sdk_php.c index d7940c624..1638719b2 100644 --- a/agent/lib_aws_sdk_php.c +++ b/agent/lib_aws_sdk_php.c @@ -367,7 +367,7 @@ void nr_lib_aws_sdk_php_lambda_handle(nr_segment_t* auto_segment, } if (nr_php_is_zval_valid_array(metadata)) { zval* uri = nr_php_zend_hash_find(Z_ARRVAL_P(metadata), "effectiveUri"); - if (nr_php_is_zval_valid_string(uri) && !nr_strempty(Z_STRVAL_P(uri))) { + if (nr_php_is_zval_non_empty_string(uri)) { external_params.uri = Z_STRVAL_P(uri); } } @@ -410,7 +410,7 @@ void nr_aws_sdk_lambda_client_invoke_parse_args(NR_EXECUTE_PROTO, nr_segment_clo return; } zval* lambda_name = nr_php_zend_hash_find(Z_ARRVAL_P(lambda_args), "FunctionName"); - if (!nr_php_is_zval_valid_string(lambda_name)) { + if (!nr_php_is_zval_non_empty_string(lambda_name)) { return; } From fbf408fb81c7a6265e5978c6b0e51910fb1f79aa Mon Sep 17 00:00:00 2001 From: Zach Neumann Date: Thu, 6 Mar 2025 12:08:10 -0700 Subject: [PATCH 18/18] fix --- agent/lib_aws_sdk_php.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/agent/lib_aws_sdk_php.c b/agent/lib_aws_sdk_php.c index 1638719b2..02a0ef4bb 100644 --- a/agent/lib_aws_sdk_php.c +++ b/agent/lib_aws_sdk_php.c @@ -392,7 +392,7 @@ void nr_aws_sdk_mshutdown(void) { void nr_aws_sdk_lambda_client_invoke_parse_args(NR_EXECUTE_PROTO, nr_segment_cloud_attrs_t* cloud_attrs) { zval* call_args = nr_php_get_user_func_arg(2, NR_EXECUTE_ORIG_ARGS); - zval* this_obj = getThis();//NR_PHP_USER_FN_THIS(); + zval* this_obj = NR_PHP_USER_FN_THIS(); char* arn = NULL; char* function_name = NULL; char* region = NULL;