Skip to content

Commit b51fbad

Browse files
committed
lambda instrumentation
1 parent f02c410 commit b51fbad

File tree

7 files changed

+213
-1
lines changed

7 files changed

+213
-1
lines changed

agent/lib_aws_sdk_php.c

Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,16 @@
1515
#include "fw_support.h"
1616
#include "util_logging.h"
1717
#include "nr_segment_message.h"
18+
#include "nr_segment_external.h"
1819
#include "lib_aws_sdk_php.h"
1920

2021
#define PHP_PACKAGE_NAME "aws/aws-sdk-php"
22+
#define AWS_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-]+))?"
2128

2229
#if ZEND_MODULE_API_NO >= ZEND_8_1_X_API_NO /* PHP8.1+ */
2330
/* Service instrumentation only supported above PHP 8.1+*/
@@ -295,6 +302,167 @@ void nr_lib_aws_sdk_php_sqs_parse_queueurl(
295302
cloud_attrs->cloud_region = region;
296303
}
297304

305+
void nr_lib_aws_sdk_php_lambda_handle(nr_segment_t* auto_segment,
306+
char* command_name_string,
307+
size_t command_name_len,
308+
NR_EXECUTE_PROTO) {
309+
nr_segment_t* external_segment = NULL;
310+
zval** retval_ptr = NR_GET_RETURN_VALUE_PTR;
311+
312+
nr_segment_cloud_attrs_t cloud_attrs = {
313+
.cloud_platform = "aws_lambda"
314+
};
315+
316+
if (NULL == auto_segment) {
317+
return;
318+
}
319+
320+
if (NULL == command_name_string || 0 == command_name_len) {
321+
return;
322+
}
323+
324+
if (NULL == retval_ptr) {
325+
/* Do not instrument when an exception has happened */
326+
return;
327+
}
328+
329+
#define AWS_COMMAND_IS(CMD) \
330+
(command_name_len == (sizeof(CMD) - 1) && nr_streq(CMD, command_name_string))
331+
332+
/* Determine if we instrument this command. */
333+
if (AWS_COMMAND_IS("invoke")) {
334+
// Command can be saved here if in the future
335+
// we instrument more than 1 lambda command
336+
} else {
337+
return;
338+
}
339+
#undef AWS_COMMAND_IS
340+
341+
/* reconstruct the ARN */
342+
nr_aws_lambda_invoke(NR_EXECUTE_ORIG_ARGS, &cloud_attrs);
343+
if (!cloud_attrs.cloud_resource_id) {
344+
/* we do not want to instrument if we cannot reconstruct the ARN */
345+
return;
346+
}
347+
348+
/*
349+
* By this point, it's been determined that this call will be instrumented so
350+
* only create the segment now, grab the parent segment start time, add our
351+
* special segment attributes/metrics then close the newly created segment.
352+
*/
353+
external_segment = nr_segment_start(NRPRG(txn), NULL, NULL);
354+
if (NULL == external_segment) {
355+
return;
356+
}
357+
/* re-use start time from auto_segment started in func_begin */
358+
external_segment->start_time = auto_segment->start_time;
359+
cloud_attrs.aws_operation = command_name_string;
360+
361+
/* end the segment */
362+
nr_segment_traces_add_cloud_attributes(external_segment, &cloud_attrs);
363+
nr_segment_external_params_t external_params = {.library = "aws_sdk"};
364+
zval* data = nr_php_get_zval_object_property(*retval_ptr, "data");
365+
if (NULL != data && IS_ARRAY == Z_TYPE_P(data)) {
366+
zval* status_code = nr_php_zend_hash_find(Z_ARRVAL_P(data), "StatusCode");
367+
if (NULL != status_code && IS_LONG == Z_TYPE_P(status_code)) {
368+
external_params.status = Z_LVAL_P(status_code);
369+
}
370+
zval* metadata = nr_php_zend_hash_find(Z_ARRVAL_P(data), "@metadata");
371+
if (NULL != metadata && IS_REFERENCE == Z_TYPE_P(metadata)
372+
&& IS_ARRAY == Z_TYPE_P(Z_REFVAL_P(metadata))) {
373+
zval* uri = nr_php_zend_hash_find(Z_ARRVAL_P(Z_REFVAL_P(metadata)), "effectiveUri");
374+
if (NULL != uri && IS_STRING == Z_TYPE_P(uri)) {
375+
external_params.uri = Z_STRVAL_P(uri);
376+
}
377+
}
378+
}
379+
nr_segment_external_end(&auto_segment, &external_params);
380+
}
381+
382+
void nr_aws_lambda_invoke(NR_EXECUTE_PROTO, nr_segment_cloud_attrs_t* cloud_attrs) {
383+
zval* call_args = nr_php_get_user_func_arg(2, NR_EXECUTE_ORIG_ARGS);
384+
zval* this_obj = NR_PHP_USER_FN_THIS();
385+
char* arn = NULL;
386+
char* function_name = NULL;
387+
char* region = NULL;
388+
zval* region_zval = NULL;
389+
char* qualifier = NULL;
390+
zval* qualifier_zval = NULL;
391+
char* accountID = NULL;
392+
393+
/* verify arguments */
394+
if (NULL == call_args || IS_ARRAY != Z_TYPE_P(call_args)) {
395+
return;
396+
}
397+
zval* lambda_args = nr_php_zend_hash_index_find(Z_ARRVAL_P(call_args), 0);
398+
if (NULL == lambda_args || IS_ARRAY != Z_TYPE_P(lambda_args)) {
399+
return;
400+
}
401+
zval* lambda_name = nr_php_zend_hash_find(Z_ARRVAL_P(lambda_args), "FunctionName");
402+
if (NULL == lambda_name || IS_STRING != Z_TYPE_P(lambda_name)) {
403+
return;
404+
}
405+
406+
/* Compile the regex */
407+
if (NULL == NRPRG(aws_arn_regex)) {
408+
NRPRG(aws_arn_regex) = nr_regex_create(AWS_ARN_REGEX, 0, 0);
409+
}
410+
411+
/* Extract all information possible from the passed lambda name via regex */
412+
nr_regex_substrings_t* matches =
413+
nr_regex_match_capture(NRPRG(aws_arn_regex),
414+
Z_STRVAL_P(lambda_name),
415+
Z_STRLEN_P(lambda_name));
416+
function_name = nr_regex_substrings_get_named(matches, "functionName");
417+
accountID = nr_regex_substrings_get_named(matches, "accountId");
418+
region = nr_regex_substrings_get_named(matches, "region");
419+
qualifier = nr_regex_substrings_get_named(matches, "qualifier");
420+
421+
/* suppliment missing information with API calls */
422+
if (nr_strempty(function_name)) {
423+
/*
424+
* Cannot get the needed data. Function name is required in the
425+
* argument, so this won't happen in normal operation
426+
*/
427+
nr_regex_substrings_destroy(&matches);
428+
return;
429+
}
430+
if (nr_strempty(accountID)) {
431+
accountID = NRINI(aws_account_id);
432+
accountID = "012345";
433+
}
434+
if (nr_strempty(region)) {
435+
region_zval = nr_php_call(this_obj, "getRegion");
436+
if (nr_php_is_zval_valid_string(region_zval)) {
437+
region = Z_STRVAL_P(region_zval);
438+
}
439+
}
440+
if (nr_strempty(qualifier)) {
441+
qualifier_zval = NULL;//nr_php_call(this_obj, "getQualifier");
442+
if (nr_php_is_zval_valid_string(qualifier_zval)) {
443+
qualifier = Z_STRVAL_P(qualifier_zval);
444+
}
445+
}
446+
447+
if (!nr_strempty(accountID) && !nr_strempty(region)) {
448+
// construct the ARN
449+
if (qualifier) {
450+
arn = nr_formatf("arn:aws:lambda:%s:%s:function:%s:%s",
451+
region, accountID, function_name, qualifier);
452+
} else {
453+
arn = nr_formatf("arn:aws:lambda:%s:%s:function:%s",
454+
region, accountID, function_name);
455+
}
456+
457+
// Attatch the ARN
458+
cloud_attrs->cloud_resource_id = arn;
459+
}
460+
461+
nr_regex_substrings_destroy(&matches);
462+
nr_php_zval_free(&region_zval);
463+
nr_php_zval_free(&qualifier_zval);
464+
}
465+
298466
char* nr_lib_aws_sdk_php_get_command_arg_value(char* command_arg_name,
299467
NR_EXECUTE_PROTO) {
300468
zval* param_array = NULL;
@@ -383,6 +551,10 @@ NR_PHP_WRAPPER(nr_aws_client_call) {
383551
nr_lib_aws_sdk_php_sqs_handle(auto_segment, command_name_string,
384552
Z_STRLEN_P(command_name),
385553
NR_EXECUTE_ORIG_ARGS);
554+
} else if (AWS_CLASS_IS("Aws\\Lambda\\LambdaClient", "LambdaClient")) {
555+
nr_lib_aws_sdk_php_lambda_handle(auto_segment, command_name_string,
556+
Z_STRLEN_P(command_name),
557+
NR_EXECUTE_ORIG_ARGS);
386558
}
387559

388560
#undef AWS_CLASS_IS
@@ -566,5 +738,6 @@ void nr_aws_sdk_php_enable() {
566738
nr_php_wrap_user_function_before_after_clean(
567739
NR_PSTR("Aws\\AwsClient::__call"), NULL, nr_aws_client_call,
568740
nr_aws_client_call);
741+
569742
#endif
570743
}

agent/lib_aws_sdk_php.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,18 @@ extern void nr_lib_aws_sdk_php_sqs_handle(nr_segment_t* segment,
7474
size_t command_name_len,
7575
NR_EXECUTE_PROTO);
7676

77+
/*
78+
* Purpose : Handle when a LambdaClient::invoke command happens
79+
*
80+
* Params : 1. NR_EXECUTE_ORIG_ARGS (execute_data, func_return_value)
81+
* 2. cloud_attrs : the cloud attributes pointer to be
82+
* populated with the ARN
83+
*
84+
* Returns :
85+
*
86+
*/
87+
void nr_aws_lambda_invoke(NR_EXECUTE_PROTO, nr_segment_cloud_attrs_t* cloud_attrs);
88+
7789
/*
7890
* Purpose : The second argument to the Aws/AwsClient::__call function should be
7991
* an array, the first element of which is itself an array of arguments that

agent/php_newrelic.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,6 @@ bool wordpress_core; /* set based on
328328
nrinistr_t
329329
wordpress_hooks_skip_filename; /* newrelic.framework.wordpress.hooks_skip_filename
330330
*/
331-
332331
nrinibool_t
333332
analytics_events_enabled; /* DEPRECATED newrelic.analytics_events.enabled */
334333
nrinibool_t
@@ -383,6 +382,11 @@ nrinibool_t
383382
nrinibool_t
384383
database_name_reporting_enabled; /* newrelic.datastore_tracer.database_name_reporting.enabled
385384
*/
385+
/*
386+
* Cloud relationship settings
387+
*/
388+
nrinistr_t
389+
aws_account_id; /* newrelic.cloud.aws.account_id */
386390

387391
/*
388392
* Deprecated settings that control request parameter capture.
@@ -464,6 +468,7 @@ nr_stack_t wordpress_tag_states; /* stack of bools indicating
464468
bool check_cufa; /* Whether we need to check cufa because we are
465469
instrumenting hooks, or whether we can skip cufa */
466470
char* wordpress_tag; /* The current WordPress tag */
471+
467472
#endif //OAPI
468473

469474
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
481486

482487
nrphpcufafn_t
483488
cufa_callback; /* The current call_user_func_array callback, if any */
489+
490+
nr_regex_t* aws_arn_regex; /* The compiled regex to search for ARNs */
484491
/*
485492
* We instrument database connection constructors and store the instance
486493
* information in a hash keyed by a string containing the connection resource

agent/php_nrini.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3100,6 +3100,18 @@ STD_PHP_INI_ENTRY_EX("newrelic.vulnerability_management.composer_api.enabled",
31003100
newrelic_globals,
31013101
nr_enabled_disabled_dh)
31023102

3103+
/*
3104+
* Cloud relationship settings
3105+
*/
3106+
STD_PHP_INI_ENTRY_EX("newrelic.cloud.aws.account_id",
3107+
"1",
3108+
NR_PHP_REQUEST,
3109+
nr_string_mh,
3110+
aws_account_id,
3111+
zend_newrelic_globals,
3112+
newrelic_globals,
3113+
0)
3114+
31033115
/*
31043116
* Messaging API
31053117
*/

axiom/nr_segment.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,8 @@ typedef struct _nr_segment_cloud_attrs_t {
159159
relationship.*/
160160
char* cloud_account_id; /*The cloud provider account ID. Needed for SQS
161161
relationship.*/
162+
char* cloud_platform; /*The platform hosting the cloud. Needed for Lambda
163+
relationship.*/
162164
char* cloud_resource_id; /*Unique cloud provider identifier. For AWS, this is
163165
the ARN of the AWS resource being accessed.*/
164166
char* aws_operation; /*AWS specific operation name.*/

axiom/nr_segment_traces.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -658,4 +658,9 @@ extern void nr_segment_traces_add_cloud_attributes(
658658
segment->attributes, NR_CLOUD_AGENT_ATTRIBUTE_DESTINATION,
659659
NR_ATTR_AWS_OPERATION, cloud_attrs->aws_operation);
660660
}
661+
if (!nr_strempty(cloud_attrs->cloud_platform)) {
662+
nr_attributes_agent_add_string(
663+
segment->attributes, NR_CLOUD_AGENT_ATTRIBUTE_DESTINATION,
664+
NR_ATTR_CLOUD_PLATFORM, cloud_attrs->cloud_platform);
665+
}
661666
}

axiom/nr_span_event.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#define NR_ATTR_CLOUD_REGION "cloud.region"
2525
#define NR_ATTR_CLOUD_ACCOUNT_ID "cloud.account.id"
2626
#define NR_ATTR_CLOUD_RESOURCE_ID "cloud.resource_id"
27+
#define NR_ATTR_CLOUD_PLATFORM "cloud.platform"
2728
#define NR_ATTR_AWS_OPERATION "aws.operation"
2829

2930
typedef struct _nr_span_event_t nr_span_event_t;

0 commit comments

Comments
 (0)