diff --git a/agent/lib_aws_sdk_php.c b/agent/lib_aws_sdk_php.c index 51ef17fda..8e4614d92 100644 --- a/agent/lib_aws_sdk_php.c +++ b/agent/lib_aws_sdk_php.c @@ -15,8 +15,15 @@ #include "fw_support.h" #include "util_logging.h" #include "lib_aws_sdk_php.h" +#include "nr_segment_external.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-]+))?" /* * In a normal course of events, the following line will always work @@ -115,6 +122,103 @@ NR_PHP_WRAPPER(nr_create_aws_service_metric) { } NR_PHP_WRAPPER_END +/* + * LambdaClient::invoke + * This is called when an instance is invoking a lambda function + * for AWS serveless execution. + */ +NR_PHP_WRAPPER(nr_aws_lambda_invoke_before) { + nr_segment_t* segment = nr_segment_start(NRPRG(txn), NULL, NULL); + segment->wraprec = auto_segment->wraprec; +} +NR_PHP_WRAPPER_END + +NR_PHP_WRAPPER(nr_aws_lambda_invoke_after) { + nr_segment_external_params_t external_params + = {.library = "aws_sdk"}; + + zval** retval_ptr = NR_GET_RETURN_VALUE_PTR; + + // Only instrument on successful invokation + if (retval_ptr != NULL) { + char* arn = NULL; + char* function_name = NULL; + char* region = NULL; + char* qualifier = NULL; + char* accountID = NULL; + zval* this_obj = NULL; + + // Extract all information possible from the passed lambda identifier + zval* lambda_arg = nr_php_get_user_func_arg(1, NR_EXECUTE_ORIG_ARGS); + nr_regex_substrings_t* matches = nr_regex_match_capture(NRPRG(aws_arn_regex), + Z_STRVAL_P(lambda_arg), + Z_STRLEN_P(lambda_arg)); + 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"); + + this_obj = NR_PHP_USER_FN_THIS(); + + // suppliment missing information with API calls + if (function_name == NULL) { + // Cannot get the needed data. Function name is required in the + // argument, so this won't happen in normal operation + } else { + if (accountID == NULL) { + accountID = NRINI(aws_account_id); + } + if (region == NULL) { + zval* region_zval = nr_php_call(this_obj, "getRegion"); + if (region_zval) { + region = Z_STRVAL_P(region_zval); + } + } + if (qualifier == NULL) { + zval* qualifier_zval = nr_php_call(this_obj, "getQualifier"); + if (qualifier_zval) { + qualifier = Z_STRVAL_P(qualifier_zval); + } + } + } + + // construct the arn + if (accountID && region) { + 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); + } + + // end the segment + external_params.status = Z_LVAL_P(nr_php_zend_hash_find(Z_ARRVAL_P(*retval_ptr), "StatusCode")); + zval* metadata = nr_php_zend_hash_find(Z_ARRVAL_P(*retval_ptr), "@metadata"); + external_params.uri = Z_STRVAL_P(nr_php_zend_hash_find(Z_ARRVAL_P(metadata), "effectiveUri")); + nr_attributes_agent_add_string(auto_segment->attributes, + NR_ATTRIBUTE_DESTINATION_SPAN, "cloud.platform" , "aws_lambda"); + nr_attributes_agent_add_string(auto_segment->attributes, + NR_ATTRIBUTE_DESTINATION_SPAN, "cloud.resource_id", arn); + nr_segment_external_end(&auto_segment, &external_params); + } else { + nr_segment_discard(&auto_segment); + } + + nr_regex_substrings_destroy(&matches); + nr_free(arn); + nr_free(function_name); + nr_free(region); + nr_free(qualifier); + nr_free(accountID); + } else { + nr_segment_discard(&auto_segment); + } +} +NR_PHP_WRAPPER_END + +static nr_regex_t* compile_arn_regex() { + return nr_regex_create(AWS_ARN_REGEX, 0, 0); +} + /* * The ideal file to begin immediate detection of the aws-sdk is: * aws-sdk-php/src/functions.php @@ -158,8 +262,14 @@ void nr_aws_sdk_php_enable() { /* Extract the version for aws-sdk 3+ */ nr_lib_aws_sdk_php_handle_version(); + NRPRG(aws_arn_regex) = compile_arn_regex(); /* Called when initializing all Clients */ nr_php_wrap_user_function(NR_PSTR("Aws\\AwsClient::parseClass"), nr_create_aws_service_metric); + nr_php_wrap_user_function_before_after_clean( + NR_PSTR("Aws\\Lambda\\LambdaClient::invoke"), + nr_aws_lambda_invoke_before, + nr_aws_lambda_invoke_after, + nr_aws_lambda_invoke_after); } diff --git a/agent/php_newrelic.h b/agent/php_newrelic.h index 6afaf531a..ffe6b46c8 100644 --- a/agent/php_newrelic.h +++ b/agent/php_newrelic.h @@ -466,6 +466,9 @@ bool check_cufa; /* Whether we need to check cufa because we are char* wordpress_tag; /* The current WordPress tag */ #endif //OAPI +nr_regex_t* aws_arn_regex; /* Regex pattern for AWS ARNs */ +nrinistr_t aws_account_id; /* TBD */ + nr_matcher_t* wordpress_plugin_matcher; /* Matcher for plugin filenames */ nr_matcher_t* wordpress_theme_matcher; /* Matcher for theme filenames */ nr_matcher_t* wordpress_core_matcher; /* Matcher for plugin filenames */ diff --git a/agent/php_rshutdown.c b/agent/php_rshutdown.c index 24b1311e4..283d2381f 100644 --- a/agent/php_rshutdown.c +++ b/agent/php_rshutdown.c @@ -104,6 +104,8 @@ int nr_php_post_deactivate(void) { nr_php_exception_filters_destroy(&NRPRG(exception_filters)); + nr_regex_destroy(&NRPRG(aws_arn_regex)); + nr_matcher_destroy(&NRPRG(wordpress_plugin_matcher)); nr_matcher_destroy(&NRPRG(wordpress_core_matcher)); nr_matcher_destroy(&NRPRG(wordpress_theme_matcher));