diff --git a/agent/php_newrelic.h b/agent/php_newrelic.h index 1860b5167..6afaf531a 100644 --- a/agent/php_newrelic.h +++ b/agent/php_newrelic.h @@ -596,6 +596,12 @@ nrinibool_t nrinibool_t vulnerability_management_composer_api_enabled; /* newrelic.vulnerability_management.composer_api.enabled */ +/* + * Configuration options for recording Messaging APIs + */ +nrinibool_t + message_tracer_segment_parameters_enabled; /* newrelic.segment_tracer.segment_parameters.enabled */ + #if ZEND_MODULE_API_NO < ZEND_7_4_X_API_NO /* * pid and user_function_wrappers are used to store user function wrappers. diff --git a/agent/php_nrini.c b/agent/php_nrini.c index 6c21d1bdf..4a2f7c471 100644 --- a/agent/php_nrini.c +++ b/agent/php_nrini.c @@ -3100,6 +3100,17 @@ STD_PHP_INI_ENTRY_EX("newrelic.vulnerability_management.composer_api.enabled", newrelic_globals, nr_enabled_disabled_dh) +/* + * Messaging API + */ +STD_PHP_INI_ENTRY_EX("newrelic.message_tracer.segment_parameters.enabled", + "1", + NR_PHP_REQUEST, + nr_boolean_mh, + message_tracer_segment_parameters_enabled, + zend_newrelic_globals, + newrelic_globals, + nr_enabled_disabled_dh) PHP_INI_END() /* } */ void nr_php_register_ini_entries(int module_number TSRMLS_DC) { diff --git a/agent/php_txn.c b/agent/php_txn.c index b5b57975c..bc0ea6b7e 100644 --- a/agent/php_txn.c +++ b/agent/php_txn.c @@ -854,6 +854,8 @@ nr_status_t nr_php_txn_begin(const char* appnames, opts.log_forwarding_log_level = NRINI(log_forwarding_log_level); opts.log_events_max_samples_stored = NRINI(log_events_max_samples_stored); opts.log_metrics_enabled = NRINI(log_metrics_enabled); + opts.message_tracer_segment_parameters_enabled + = NRINI(message_tracer_segment_parameters_enabled); /* * Enable the behaviour whereby asynchronous time is discounted from the total @@ -1165,7 +1167,7 @@ nr_status_t nr_php_txn_end(int ignoretxn, int in_post_deactivate TSRMLS_DC) { #if ZEND_MODULE_API_NO >= ZEND_8_0_X_API_NO \ && !defined OVERWRITE_ZEND_EXECUTE_DATA nr_segment_t* segment = nr_txn_get_current_segment(NRPRG(txn), NULL); - while(NULL != segment && segment != NRTXN(segment_root)) { + while (NULL != segment && segment != NRTXN(segment_root)) { nr_segment_end(&segment); segment = nr_txn_get_current_segment(NRPRG(txn), NULL); } diff --git a/agent/scripts/newrelic.ini.template b/agent/scripts/newrelic.ini.template index 4ed06a091..b406628a9 100644 --- a/agent/scripts/newrelic.ini.template +++ b/agent/scripts/newrelic.ini.template @@ -1341,3 +1341,14 @@ newrelic.daemon.logfile = "/var/log/newrelic/newrelic-daemon.log" ; to gather package information for vulnerability management. ; ;newrelic.vulnerability_management.composer_api.enabled = false + +; Setting: newrelic.message_tracer.segment_parameters.enabled +; Type : boolean +; Scope : per-directory +; Default: true +; Info : If this setting is true, then message parameters will be captured and +; stored on their respective segments. While enabled, specific attributes +; can be filtered by using newrelic.attributes.include/exclude and +; newrelic.span_events.attributes.include/exclude +; +;newrelic.message_tracer.segment_parameters.enabled = true diff --git a/axiom/Makefile b/axiom/Makefile index 34b45c229..4cce3a6f6 100644 --- a/axiom/Makefile +++ b/axiom/Makefile @@ -113,6 +113,7 @@ OBJS := \ nr_segment_children.o \ nr_segment_datastore.o \ nr_segment_external.o \ + nr_segment_message.o \ nr_segment_private.o \ nr_segment_terms.o \ nr_segment_traces.o \ diff --git a/axiom/nr_segment.c b/axiom/nr_segment.c index fa91cf1b7..4aaf7c120 100644 --- a/axiom/nr_segment.c +++ b/axiom/nr_segment.c @@ -313,6 +313,26 @@ static void nr_populate_http_spans(nr_span_event_t* span_event, segment->typed_attributes->external.status); } +static void nr_populate_message_spans(nr_span_event_t* span_event, + const nr_segment_t* segment) { + nr_span_event_set_category(span_event, NR_SPAN_MESSAGE); + + if (nrunlikely(NULL == segment || NULL == segment->typed_attributes)) { + return; + } + + nr_span_event_set_spankind(span_event, + segment->typed_attributes->message.message_action); + nr_span_event_set_message( + span_event, NR_SPAN_MESSAGE_DESTINATION_NAME, + segment->typed_attributes->message.destination_name); + nr_span_event_set_message( + span_event, NR_SPAN_MESSAGE_MESSAGING_SYSTEM, + segment->typed_attributes->message.messaging_system); + nr_span_event_set_message(span_event, NR_SPAN_MESSAGE_SERVER_ADDRESS, + segment->typed_attributes->message.server_address); +} + static nr_status_t add_user_attribute_to_span_event(const char* key, const nrobj_t* val, void* ptr) { @@ -431,8 +451,8 @@ nr_span_event_t* nr_segment_to_span_event(nr_segment_t* segment) { nr_span_event_set_trusted_parent_id( event, nr_distributed_trace_inbound_get_trusted_parent_id( segment->txn->distributed_trace)); - nr_span_event_set_parent_id(event, - nr_distributed_trace_inbound_get_guid(segment->txn->distributed_trace)); + nr_span_event_set_parent_id(event, nr_distributed_trace_inbound_get_guid( + segment->txn->distributed_trace)); nr_span_event_set_transaction_name(event, segment->txn->name); @@ -482,6 +502,10 @@ nr_span_event_t* nr_segment_to_span_event(nr_segment_t* segment) { nr_populate_http_spans(event, segment); break; + case NR_SEGMENT_MESSAGE: + nr_populate_message_spans(event, segment); + break; + case NR_SEGMENT_CUSTOM: nr_span_event_set_category(event, NR_SPAN_GENERIC); break; @@ -599,6 +623,30 @@ bool nr_segment_set_external(nr_segment_t* segment, return true; } +bool nr_segment_set_message(nr_segment_t* segment, + const nr_segment_message_t* message) { + if (nrunlikely((NULL == segment) || (NULL == message))) { + return false; + } + + nr_segment_destroy_typed_attributes(segment->type, + &segment->typed_attributes); + segment->type = NR_SEGMENT_MESSAGE; + segment->typed_attributes = nr_zalloc(sizeof(nr_segment_typed_attributes_t)); + + // clang-format off + // Initialize the fields of the message attributes, one field per line. + segment->typed_attributes->message = (nr_segment_message_t){ + .message_action = message->message_action, + .destination_name = nr_strempty(message->destination_name) ? NULL: nr_strdup(message->destination_name), + .messaging_system = nr_strempty(message->messaging_system) ? NULL: nr_strdup(message->messaging_system), + .server_address = nr_strempty(message->server_address) ? NULL: nr_strdup(message->server_address), + }; + // clang-format on + + return true; +} + bool nr_segment_add_child(nr_segment_t* parent, nr_segment_t* child) { if (nrunlikely((NULL == parent) || (NULL == child))) { return false; diff --git a/axiom/nr_segment.h b/axiom/nr_segment.h index 56d972579..f35ab6224 100644 --- a/axiom/nr_segment.h +++ b/axiom/nr_segment.h @@ -34,7 +34,8 @@ typedef struct _nrtxn_t nrtxn_t; typedef enum _nr_segment_type_t { NR_SEGMENT_CUSTOM, NR_SEGMENT_DATASTORE, - NR_SEGMENT_EXTERNAL + NR_SEGMENT_EXTERNAL, + NR_SEGMENT_MESSAGE } nr_segment_type_t; /* @@ -109,6 +110,48 @@ typedef struct _nr_segment_external_t { uint64_t status; } nr_segment_external_t; +typedef struct _nr_segment_message_t { + /* + * Attributes needed for entity relationship building. + * Compare to OTEL attributes: + * https://opentelemetry.io/docs/specs/semconv/attributes-registry/cloud/ + * cloud.account.id, cloud.region, messaging.system and server.address are + * used to create relationships between APM and cloud services. It may not + * make sense to add these attributes unless they are used for creating one of + * the relationships in Entity Relationships. + */ + + nr_span_spankind_t + message_action; /*The action of the message, e.g.,Produce/Consume.*/ + char* destination_name; /*The name of the Queue, Topic, or Exchange; + otherwise, Temp. Needed for SQS relationship.*/ + char* messaging_system; /* for ex: aws_sqs. Needed for SQS relationship.*/ + char* server_address; /*The server domain name or IP address. Needed for + MQBROKER relationship.*/ +} nr_segment_message_t; + +typedef struct _nr_segment_cloud_attrs_t { + /* + * Attributes needed for entity relationship building. + * Compare to OTEL attributes: + * https://opentelemetry.io/docs/specs/semconv/attributes-registry/cloud/ + * cloud.account.id, cloud.region, messaging.system and server.address are + * used to create relationships between APM and cloud services. It may not + * make sense to add these attributes unless they are used for creating one of + * the relationships in Entity Relationships. + * These attributes aren't specific to a segment category so don't belong as + * typed attributes and can be added whenever they are available. + */ + char* cloud_region; /*Targeted region; ex:us-east-1*. Needed for SQS + relationship.*/ + char* cloud_account_id; /*The cloud provider account ID. Needed for SQS + 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.*/ + +} nr_segment_cloud_attrs_t; + typedef struct _nr_segment_metric_t { char* name; bool scoped; @@ -132,6 +175,7 @@ typedef struct _nr_segment_error_t { typedef union { nr_segment_datastore_t datastore; nr_segment_external_t external; + nr_segment_message_t message; } nr_segment_typed_attributes_t; typedef struct _nr_segment_t { @@ -179,8 +223,8 @@ typedef struct _nr_segment_t { int priority; /* Used to determine which segments are preferred for span event creation */ nr_segment_typed_attributes_t* typed_attributes; /* Attributes specific to - external or datastore - segments. */ + external, datastore, + or message segments. */ nr_segment_error_t* error; /* segment error attributes */ #if ZEND_MODULE_API_NO >= ZEND_8_0_X_API_NO \ && !defined OVERWRITE_ZEND_EXECUTE_DATA /* PHP 8.0+ and OAPI */ @@ -314,6 +358,17 @@ extern bool nr_segment_set_datastore(nr_segment_t* segment, */ extern bool nr_segment_set_external(nr_segment_t* segment, const nr_segment_external_t* external); + +/* + * Purpose : Mark the segment as being a message segment. + * + * Params : 1. The pointer to the segment. + * 2. The message attributes, which will be copied into the segment. + * + * Returns : true if successful, false otherwise. + */ +extern bool nr_segment_set_message(nr_segment_t* segment, + const nr_segment_message_t* message); /* * Purpose : Add a child to a segment. * diff --git a/axiom/nr_segment_external.c b/axiom/nr_segment_external.c index dbaf4c78a..738cec6f5 100644 --- a/axiom/nr_segment_external.c +++ b/axiom/nr_segment_external.c @@ -57,8 +57,8 @@ static void nr_segment_external_set_attrs( * External/{host}/all non-CAT * ExternalTransaction/{host}/{external_id}/{external_txnname} CAT * - * These metrics are dictated by the spec located here: - * https://source.datanerd.us/agents/agent-specs/blob/master/Cross-Application-Tracing-PORTED.md + * These metrics are dictated by the agent-spec in this file: + * Cross-Application-Tracing-PORTED.md */ static void nr_segment_external_create_metrics(nr_segment_t* segment, const char* uri, diff --git a/axiom/nr_segment_message.c b/axiom/nr_segment_message.c new file mode 100644 index 000000000..56e9ed2b9 --- /dev/null +++ b/axiom/nr_segment_message.c @@ -0,0 +1,238 @@ +/* + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "nr_axiom.h" + +#include + +#include "nr_header.h" +#include "nr_segment_message.h" +#include "nr_segment_private.h" +#include "util_strings.h" +#include "util_url.h" + +/* + * Purpose : Set all the typed message attributes on the segment. + * + * Params : 1. nr_segment_t* ASSUMED TO BE NON-NULL - the segment to set the + * attributes on + * 2. nr_segment_message_params_t* ASSUMED TO BE NON-NULL - the + * parameters set the attributes to + * + * Returns: true on success. + * + * Note: This is a function private to this file and assumes the calling + * function has already checked the input parameters for NULL prior to calling + * this function. Calling function is assumed to check the following items for + * NULL: if (NULL == segment || NULL == message_params || NULL == segment->txn) + */ +static void nr_segment_message_set_attrs( + nr_segment_t* segment, + const nr_segment_message_params_t* params) { + nr_segment_message_t message_attributes = {0}; + + message_attributes.message_action = params->message_action; + + if (segment->txn->options.message_tracer_segment_parameters_enabled) { + message_attributes.destination_name = params->destination_name; + message_attributes.messaging_system = params->messaging_system; + message_attributes.server_address = params->server_address; + } + + nr_segment_set_message(segment, &message_attributes); +} + +/* + * Purpose : Create metrics for a completed message call and set the segment + * name. + * + * Metrics created during this call + * ---------------------------------------------------------------------------------- + * MessageBroker/all Unscoped Always + * MessageBroker/{library}/all Scoped Always + * + * Metrics created based on MessageBroker/all (in nr_txn_create_rollup_metrics) + * ---------------------------------------------------------------------------------- + * MessageBroker/allWeb Unscoped Web + * MessageBroker/allOther Unscoped non-Web + * + * Segment name + * ----------------------------------------------------------------------------------- + * MessageBroker/{library}/all Always + * For non-temp: + * MessageBroker/{Library}/{DestinationType}/{Action}/Named/{DestinationName} + * For temp: + * MessageBroker/{Library}/{DestinationType}/{Action}/Temp + * + * + * These metrics are dictated by the agent-spec file here: + * APIs/messaging.md#metrics + * When the destination is temporary (such as a temporary queue, or a temporary + * topic), the destination name MUST be omitted. The metric segment 'Named' MUST + * be replaced with 'Temp'. The DestinationType segment SHOULD NOT contain + * "Temporary". Thus, "Temporary " should be removed from the destination type + * enum before metric use. Examples: MessageBroker/JMS/Queue/Produce/Temp, + * MessageBroker/JMS/Topic/Produce/Temp + * + * Further note that for pull-style messaging, the transaction segment name MUST + * be equal to the scoped metric name (e.g., + * MessageBroker/JMS/Queue/Produce/Named/SortQueue) + * + * + * Params : 1. The message segment. + * 2. Message parameters + * 3. Duration of the segment + * + * Returns : the scoped metric that was created. Caller is responsible for + * freeing this value. + */ + +static char* nr_segment_message_create_metrics( + nr_segment_t* segment, + const nr_segment_message_params_t* message_params, + nrtime_t duration) { + const char* action_string = NULL; + const char* destination_type_string = NULL; + const char* library_string = NULL; + char* rollup_metric = NULL; + char* scoped_metric = NULL; + + if (NULL == segment) { + return NULL; + } + + if (NULL == message_params) { + return NULL; + } + + /* Rollup metric. + * + * This has to be created on the transaction in order to create + * MessageBroker/allWeb and MessageBroker/allOther and to calculate + * messageDuration later on. + */ + + nrm_force_add(segment->txn->unscoped_metrics, "MessageBroker/all", duration); + + if (nr_strempty(message_params->library)) { + library_string = ""; + } else { + library_string = message_params->library; + } + rollup_metric = nr_formatf("MessageBroker/%s/all", library_string); + nrm_force_add(segment->txn->unscoped_metrics, rollup_metric, duration); + nr_free(rollup_metric); + + /* + * Note: although the concept of Temporary queues/topics is detailed in the + * spec, in practice, we are unlikely to encounter it as it is currently only + * meaningful with JMS (Java Message Service). It is added here for adherence + * with spec. + */ + + if (NR_SPANKIND_PRODUCER == message_params->message_action) { + action_string = "Produce"; + } else if (NR_SPANKIND_CONSUMER == message_params->message_action) { + action_string = "Consume"; + } else { + action_string = ""; + } + + switch (message_params->destination_type) { + case NR_MESSAGE_DESTINATION_TYPE_TEMP_QUEUE: + case NR_MESSAGE_DESTINATION_TYPE_QUEUE: + destination_type_string = "Queue"; + break; + case NR_MESSAGE_DESTINATION_TYPE_TEMP_TOPIC: + case NR_MESSAGE_DESTINATION_TYPE_TOPIC: + destination_type_string = "Topic"; + break; + case NR_MESSAGE_DESTINATION_TYPE_EXCHANGE: + destination_type_string = "Exchange"; + break; + default: + destination_type_string = ""; + break; + } + /* + * Create the scoped metric + * MessageBroker/{Library}/{DestinationType}/{Action}/Named/{DestinationName} + * non-temp MessageBroker/{Library}/{DestinationType}/{Action}/Temp + */ + if (NR_MESSAGE_DESTINATION_TYPE_TEMP_QUEUE == message_params->destination_type + || NR_MESSAGE_DESTINATION_TYPE_TEMP_TOPIC + == message_params->destination_type) { + scoped_metric = nr_formatf("MessageBroker/%s/%s/%s/Temp", library_string, + destination_type_string, action_string); + } else { + scoped_metric + = nr_formatf("MessageBroker/%s/%s/%s/Named/%s", library_string, + destination_type_string, action_string, + nr_strempty(message_params->destination_name) + ? "" + : message_params->destination_name); + } + + nr_segment_add_metric(segment, scoped_metric, true); + + /* + * The scoped metric will be used as the segment name. + */ + return scoped_metric; +} + +bool nr_segment_message_end(nr_segment_t** segment_ptr, + const nr_segment_message_params_t* message_params) { + bool rv = false; + nr_segment_t* segment; + nrtime_t duration = 0; + char* scoped_metric = NULL; + + if (NULL == segment_ptr) { + return false; + } + + segment = *segment_ptr; + + if (NULL == segment || NULL == message_params || NULL == segment->txn) { + return false; + } + + /* + * We don't want message segments to have any children, as + * this would scramble the exclusive time calculation. + * Additionally, because it makes http calls under the hood, + * we don't want additional external calls created for this same txn. + * Therefore, we delete all children of the message segment. + */ + if (segment) { + for (size_t i = 0; i < nr_segment_children_size(&segment->children); i++) { + nr_segment_t* child = nr_segment_children_get(&segment->children, i); + nr_segment_discard(&child); + } + } + + nr_segment_message_set_attrs(segment, message_params); + + /* + * We set the end time here because we need the duration, (nr_segment_end will + * not overwrite this value if it's already set). + */ + if (!segment->stop_time) { + segment->stop_time + = nr_time_duration(nr_txn_start_time(segment->txn), nr_get_time()); + } + duration = nr_time_duration(segment->start_time, segment->stop_time); + + scoped_metric + = nr_segment_message_create_metrics(segment, message_params, duration); + nr_segment_set_name(segment, scoped_metric); + + rv = nr_segment_end(&segment); + + nr_free(scoped_metric); + + return rv; +} diff --git a/axiom/nr_segment_message.h b/axiom/nr_segment_message.h new file mode 100644 index 000000000..2104e3f35 --- /dev/null +++ b/axiom/nr_segment_message.h @@ -0,0 +1,53 @@ +/* + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef NR_SEGMENT_MESSAGE_HDR +#define NR_SEGMENT_MESSAGE_HDR + +#include "nr_segment.h" +#include "nr_segment_traces.h" + +/* + * Note: + * CAT is EOLed and this feature is not compatible with CAT. + */ + +typedef enum _nr_segment_message_destination_type_t { + NR_MESSAGE_DESTINATION_TYPE_QUEUE, + NR_MESSAGE_DESTINATION_TYPE_TOPIC, + NR_MESSAGE_DESTINATION_TYPE_TEMP_QUEUE, + NR_MESSAGE_DESTINATION_TYPE_TEMP_TOPIC, + NR_MESSAGE_DESTINATION_TYPE_EXCHANGE +} nr_segment_message_destination_type_t; + +typedef struct { + /* All strings are null-terminated. When NULL/empty the values are ingored. */ + + /* Only used for creating metrics. */ + char* library; /* Library; Possible values are SQS, SNS, RabbitMQ, JMS */ + nr_segment_message_destination_type_t + destination_type; /* Named/temp queue/topic/exchange */ + + /* Used for creating message attributes. */ + nr_span_spankind_t + message_action; /*The action of the message, e.g.,Produce/Consume.*/ + char* destination_name; /*The name of the Queue, Topic, or Exchange; + otherwise, Temp. Needed for SQS relationship.*/ + char* messaging_system; /* for ex: aws_sqs. Needed for SQS relationship.*/ + char* server_address; /*The server domain name or IP address. Needed for + MQBROKER relationship.*/ +} nr_segment_message_params_t; + +/* + * Purpose : End a message segment and record metrics. + * + * Params : 1. nr_segment_message_params_t + * + * Returns: true on success. + */ +extern bool nr_segment_message_end(nr_segment_t** segment, + const nr_segment_message_params_t* params); + +#endif diff --git a/axiom/nr_segment_private.c b/axiom/nr_segment_private.c index a60865afd..f063862b7 100644 --- a/axiom/nr_segment_private.c +++ b/axiom/nr_segment_private.c @@ -39,6 +39,16 @@ void nr_segment_external_destroy_fields(nr_segment_external_t* external) { nr_free(external->procedure); } +void nr_segment_message_destroy_fields(nr_segment_message_t* message) { + if (nrunlikely(NULL == message)) { + return; + } + + nr_free(message->destination_name); + nr_free(message->messaging_system); + nr_free(message->server_address); +} + void nr_segment_destroy_typed_attributes( nr_segment_type_t type, nr_segment_typed_attributes_t** attributes) { @@ -54,6 +64,8 @@ void nr_segment_destroy_typed_attributes( nr_segment_datastore_destroy_fields(&attrs->datastore); } else if (NR_SEGMENT_EXTERNAL == type) { nr_segment_external_destroy_fields(&attrs->external); + } else if (NR_SEGMENT_MESSAGE == type) { + nr_segment_message_destroy_fields(&attrs->message); } nr_free(attrs); diff --git a/axiom/nr_segment_private.h b/axiom/nr_segment_private.h index 70b499795..b2d6fe2c4 100644 --- a/axiom/nr_segment_private.h +++ b/axiom/nr_segment_private.h @@ -33,6 +33,13 @@ void nr_segment_datastore_destroy_fields(nr_segment_datastore_t* datastore); */ void nr_segment_external_destroy_fields(nr_segment_external_t* external); +/* + * Purpose : Free all data related to a segment's message metadata. + * + * Params : 1. A pointer to a segment's nr_segment_message_t structure. + */ +void nr_segment_message_destroy_fields(nr_segment_message_t* message); + /* * Purpose : Free all data related to a segment metric. * diff --git a/axiom/nr_segment_traces.c b/axiom/nr_segment_traces.c index 846d89a55..153ccd5c2 100644 --- a/axiom/nr_segment_traces.c +++ b/axiom/nr_segment_traces.c @@ -162,6 +162,15 @@ static void add_typed_attributes_to_buffer(nrbuf_t* buf, ext->transaction_guid, false); add_hash_key_value_to_buffer_int(buf, "status", &ext->status); } break; + case NR_SEGMENT_MESSAGE: { + const nr_segment_message_t* message = &segment->typed_attributes->message; + add_hash_key_value_to_buffer(buf, "destination_name", + message->destination_name, false); + add_hash_key_value_to_buffer(buf, "messaging_system", + message->messaging_system, false); + add_hash_key_value_to_buffer(buf, "server_address", + message->server_address, false); + } break; case NR_SEGMENT_CUSTOM: default: break; @@ -578,3 +587,65 @@ void nr_segment_traces_create_data( return; } + +/* + * Purpose : If available, add cloud attributes to segment. + * + * Params : 1. segment to create and add agent attributes to + * 2. nr_segment_cloud_attrs_t* that contains the attributes + * + * Returns : void + * + */ +extern void nr_segment_traces_add_cloud_attributes( + nr_segment_t* segment, + const nr_segment_cloud_attrs_t* cloud_attrs) { + if (NULL == cloud_attrs) { + return; + } + + if (NULL == segment) { + return; + } + + /* + * Ensure a spot for the attributes. + */ + + if (NULL == segment->attributes) { + segment->attributes = nr_attributes_create(segment->txn->attribute_config); + } + + if (nrunlikely(NULL == segment->attributes)) { + return; + } + +#define NR_CLOUD_AGENT_ATTRIBUTE_DESTINATION \ + (NR_ATTRIBUTE_DESTINATION_TXN_TRACE | NR_ATTRIBUTE_DESTINATION_ERROR \ + | NR_ATTRIBUTE_DESTINATION_TXN_EVENT | NR_ATTRIBUTE_DESTINATION_SPAN) + + /* + * If the value is empty or null, ignore it. + */ + + if (!nr_strempty(cloud_attrs->cloud_region)) { + nr_attributes_agent_add_string( + segment->attributes, NR_CLOUD_AGENT_ATTRIBUTE_DESTINATION, + NR_ATTR_CLOUD_REGION, cloud_attrs->cloud_region); + } + if (!nr_strempty(cloud_attrs->cloud_account_id)) { + nr_attributes_agent_add_string( + segment->attributes, NR_CLOUD_AGENT_ATTRIBUTE_DESTINATION, + NR_ATTR_CLOUD_ACCOUNT_ID, cloud_attrs->cloud_account_id); + } + if (!nr_strempty(cloud_attrs->cloud_resource_id)) { + nr_attributes_agent_add_string( + segment->attributes, NR_CLOUD_AGENT_ATTRIBUTE_DESTINATION, + NR_ATTR_CLOUD_RESOURCE_ID, cloud_attrs->cloud_resource_id); + } + if (!nr_strempty(cloud_attrs->aws_operation)) { + nr_attributes_agent_add_string( + segment->attributes, NR_CLOUD_AGENT_ATTRIBUTE_DESTINATION, + NR_ATTR_AWS_OPERATION, cloud_attrs->aws_operation); + } +} diff --git a/axiom/nr_segment_traces.h b/axiom/nr_segment_traces.h index a8ccbb10f..aa9ad4610 100644 --- a/axiom/nr_segment_traces.h +++ b/axiom/nr_segment_traces.h @@ -124,4 +124,8 @@ bool nr_segment_traces_json_print_segments(nrbuf_t* buf, extern nr_segment_iter_return_t nr_segment_traces_stot_iterator_callback( nr_segment_t* segment, void* userdata); + +extern void nr_segment_traces_add_cloud_attributes( + nr_segment_t* segment, + const nr_segment_cloud_attrs_t* cloud_attrs); #endif diff --git a/axiom/nr_span_event.c b/axiom/nr_span_event.c index 965b297d9..00987881a 100644 --- a/axiom/nr_span_event.c +++ b/axiom/nr_span_event.c @@ -141,20 +141,49 @@ void nr_span_event_set_category(nr_span_event_t* event, switch (category) { case NR_SPAN_DATASTORE: nro_set_hash_string(event->intrinsics, "category", "datastore"); - nro_set_hash_string(event->intrinsics, "span.kind", "client"); + nr_span_event_set_spankind(event, NR_SPANKIND_CLIENT); break; case NR_SPAN_GENERIC: nro_set_hash_string(event->intrinsics, "category", "generic"); - if (nro_get_hash_value(event->intrinsics, "span.kind", NULL)) { - nro_set_hash_none(event->intrinsics, "span.kind"); - } + nr_span_event_set_spankind(event, NR_SPANKIND_NO_SPANKIND); break; case NR_SPAN_HTTP: nro_set_hash_string(event->intrinsics, "category", "http"); + nr_span_event_set_spankind(event, NR_SPANKIND_CLIENT); + break; + + case NR_SPAN_MESSAGE: + nro_set_hash_string(event->intrinsics, "category", "message"); + /* give it a default value in case we exit before spankind is set*/ + nr_span_event_set_spankind(event, NR_SPANKIND_NO_SPANKIND); + break; + } +} + +void nr_span_event_set_spankind(nr_span_event_t* event, + nr_span_spankind_t spankind) { + if (NULL == event) { + return; + } + + switch (spankind) { + case NR_SPANKIND_PRODUCER: + nro_set_hash_string(event->intrinsics, "span.kind", "producer"); + break; + case NR_SPANKIND_CLIENT: nro_set_hash_string(event->intrinsics, "span.kind", "client"); break; + case NR_SPANKIND_CONSUMER: + nro_set_hash_string(event->intrinsics, "span.kind", "consumer"); + break; + case NR_SPANKIND_NO_SPANKIND: + default: + if (nro_get_hash_value(event->intrinsics, "span.kind", NULL)) { + nro_set_hash_none(event->intrinsics, "span.kind"); + } + break; } } @@ -328,6 +357,29 @@ void nr_span_event_set_external_status(nr_span_event_t* event, nro_set_hash_ulong(event->agent_attributes, "http.statusCode", status); } +void nr_span_event_set_message(nr_span_event_t* event, + nr_span_event_message_member_t member, + const char* new_value) { + if (NULL == event || NULL == new_value) { + return; + } + + switch (member) { + case NR_SPAN_MESSAGE_DESTINATION_NAME: + nro_set_hash_string(event->agent_attributes, + NR_ATTR_MESSAGING_DESTINATION_NAME, new_value); + break; + case NR_SPAN_MESSAGE_MESSAGING_SYSTEM: + nro_set_hash_string(event->agent_attributes, NR_ATTR_MESSAGING_SYSTEM, + new_value); + break; + case NR_SPAN_MESSAGE_SERVER_ADDRESS: + nro_set_hash_string(event->agent_attributes, NR_ATTR_SERVER_ADDRESS, + new_value); + break; + } +} + /* * Getters. * @@ -378,6 +430,7 @@ SPAN_EVENT_GETTER_STRING(nr_span_event_get_transaction_name, intrinsics, "transaction.name") SPAN_EVENT_GETTER_STRING(nr_span_event_get_category, intrinsics, "category") +SPAN_EVENT_GETTER_STRING(nr_span_event_get_spankind, intrinsics, "span.kind") SPAN_EVENT_GETTER_TIME(nr_span_event_get_timestamp, intrinsics, "timestamp") SPAN_EVENT_GETTER_DOUBLE(nr_span_event_get_duration, intrinsics, "duration") SPAN_EVENT_GETTER_DOUBLE(nr_span_event_get_priority, intrinsics, "priority") @@ -466,6 +519,26 @@ const char* nr_span_event_get_external(const nr_span_event_t* event, return NULL; } +const char* nr_span_event_get_message(const nr_span_event_t* event, + nr_span_event_message_member_t member) { + if (NULL == event) { + return NULL; + } + + switch (member) { + case NR_SPAN_MESSAGE_DESTINATION_NAME: + return nro_get_hash_string(event->agent_attributes, + NR_ATTR_MESSAGING_DESTINATION_NAME, NULL); + case NR_SPAN_MESSAGE_MESSAGING_SYSTEM: + return nro_get_hash_string(event->agent_attributes, + NR_ATTR_MESSAGING_SYSTEM, NULL); + case NR_SPAN_MESSAGE_SERVER_ADDRESS: + return nro_get_hash_string(event->agent_attributes, + NR_ATTR_SERVER_ADDRESS, NULL); + } + return NULL; +} + void nr_span_event_set_attribute_user(nr_span_event_t* event, const char* name, const nrobj_t* value) { diff --git a/axiom/nr_span_event.h b/axiom/nr_span_event.h index 74ee2119c..2a87b2306 100644 --- a/axiom/nr_span_event.h +++ b/axiom/nr_span_event.h @@ -13,6 +13,14 @@ #include #include +#define NR_ATTR_MESSAGING_DESTINATION_NAME "messaging.destination.name" +#define NR_ATTR_MESSAGING_SYSTEM "messaging.system" +#define NR_ATTR_SERVER_ADDRESS "server.address" +#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_AWS_OPERATION "aws.operation" + typedef struct _nr_span_event_t nr_span_event_t; /* @@ -21,9 +29,30 @@ typedef struct _nr_span_event_t nr_span_event_t; typedef enum { NR_SPAN_GENERIC, NR_SPAN_HTTP, - NR_SPAN_DATASTORE + NR_SPAN_DATASTORE, + NR_SPAN_MESSAGE } nr_span_category_t; +/* + * The spankinds a span may fall into. + * This is set according to: + * 1) guidelines in agent-specs which state datastore and http spans set + * span.kind to client and further states that generic span.kind is unset + * + * 2) for message spans follow guidance here: + * https://opentelemetry.io/docs/specs/semconv/messaging/messaging-spans/ + * which states that span.kind is + * a) producer when the operation type is create or send(if the context is + * create) b) client when the operation type is create or send(if the context is + * NOT create) c) consumer when the operation type is process + */ +typedef enum { + NR_SPANKIND_PRODUCER, + NR_SPANKIND_CLIENT, + NR_SPANKIND_CONSUMER, + NR_SPANKIND_NO_SPANKIND +} nr_span_spankind_t; + /* * Fields that can be set on datastore spans. */ @@ -44,6 +73,15 @@ typedef enum { NR_SPAN_EXTERNAL_METHOD } nr_span_event_external_member_t; +/* + * Fields that can be set on message spans. + */ +typedef enum { + NR_SPAN_MESSAGE_DESTINATION_NAME, + NR_SPAN_MESSAGE_MESSAGING_SYSTEM, + NR_SPAN_MESSAGE_SERVER_ADDRESS, +} nr_span_event_message_member_t; + /* * The parent attributes that can be set on service entry spans. * parent.transportDuration is set in @@ -115,6 +153,8 @@ extern void nr_span_event_set_transaction_name(nr_span_event_t* event, const char* transaction_name); extern void nr_span_event_set_category(nr_span_event_t* event, nr_span_category_t category); +extern void nr_span_event_set_spankind(nr_span_event_t* event, + nr_span_spankind_t spankind); extern void nr_span_event_set_timestamp(nr_span_event_t* event, nrtime_t time); extern void nr_span_event_set_duration(nr_span_event_t* event, nrtime_t duration); @@ -170,6 +210,18 @@ extern void nr_span_event_set_external(nr_span_event_t* event, extern void nr_span_event_set_external_status(nr_span_event_t* event, const uint64_t status); +/* + * Purpose : Set a message attribute. + * + * Params : 1. The target Span Event that should be changed. + * 2. The message attribute to be set. + * 3. The string value that the field will be after the function has + * executed. + */ +extern void nr_span_event_set_message(nr_span_event_t* event, + nr_span_event_message_member_t member, + const char* new_value); + /* * Purpose : Set a user attribute. * diff --git a/axiom/nr_span_event_private.h b/axiom/nr_span_event_private.h index 349c50538..4f55c6f2f 100644 --- a/axiom/nr_span_event_private.h +++ b/axiom/nr_span_event_private.h @@ -28,6 +28,7 @@ extern const char* nr_span_event_get_name(const nr_span_event_t* event); extern const char* nr_span_event_get_transaction_name( const nr_span_event_t* event); extern const char* nr_span_event_get_category(const nr_span_event_t* event); +extern const char* nr_span_event_get_spankind(const nr_span_event_t* event); extern nrtime_t nr_span_event_get_timestamp(const nr_span_event_t* event); extern double nr_span_event_get_duration(const nr_span_event_t* event); extern double nr_span_event_get_priority(const nr_span_event_t* event); @@ -44,6 +45,9 @@ extern const char* nr_span_event_get_external( const nr_span_event_t* event, nr_span_event_external_member_t member); extern uint64_t nr_span_event_get_external_status(const nr_span_event_t* event); +extern const char* nr_span_event_get_message( + const nr_span_event_t* event, + nr_span_event_message_member_t member); extern const char* nr_span_event_get_error_message( const nr_span_event_t* event); extern const char* nr_span_event_get_error_class(const nr_span_event_t* event); diff --git a/axiom/nr_txn.c b/axiom/nr_txn.c index 4e5c4c41e..06c0214ef 100644 --- a/axiom/nr_txn.c +++ b/axiom/nr_txn.c @@ -1221,11 +1221,15 @@ void nr_txn_create_rollup_metrics(nrtxn_t* txn) { "Datastore/allOther"); nrm_duplicate_metric(txn->unscoped_metrics, "External/all", "External/allOther"); + nrm_duplicate_metric(txn->unscoped_metrics, "MessageBroker/all", + "MessageBroker/allOther"); } else { nrm_duplicate_metric(txn->unscoped_metrics, "Datastore/all", "Datastore/allWeb"); nrm_duplicate_metric(txn->unscoped_metrics, "External/all", "External/allWeb"); + nrm_duplicate_metric(txn->unscoped_metrics, "MessageBroker/all", + "MessageBroker/allWeb"); } nr_string_pool_apply( @@ -2492,11 +2496,15 @@ nr_analytics_event_t* nr_error_to_event(const nrtxn_t* txn) { "External/all", "externalDuration"); nr_txn_add_metric_total_as_attribute(params, txn->unscoped_metrics, "Datastore/all", "databaseDuration"); + nr_txn_add_metric_total_as_attribute(params, txn->unscoped_metrics, + "MessageBroker/all", "messageDuration"); nr_txn_add_metric_count_as_attribute(params, txn->unscoped_metrics, "Datastore/all", "databaseCallCount"); nr_txn_add_metric_count_as_attribute(params, txn->unscoped_metrics, "External/all", "externalCallCount"); + nr_txn_add_metric_count_as_attribute(params, txn->unscoped_metrics, + "MessageBroker/all", "messageCallCount"); nro_set_hash_string(params, "nr.transactionGuid", nr_txn_get_guid(txn)); @@ -2588,6 +2596,10 @@ nrobj_t* nr_txn_event_intrinsics(const nrtxn_t* txn) { "Datastore/all", "databaseDuration"); nr_txn_add_metric_count_as_attribute(params, txn->unscoped_metrics, "Datastore/all", "databaseCallCount"); + nr_txn_add_metric_total_as_attribute(params, txn->unscoped_metrics, + "MessageBroker/all", "messageDuration"); + nr_txn_add_metric_count_as_attribute(params, txn->unscoped_metrics, + "MessageBroker/all", "messageCallCount"); if (txn->options.distributed_tracing_enabled) { nr_txn_add_distributed_tracing_intrinsics(txn, params); diff --git a/axiom/nr_txn.h b/axiom/nr_txn.h index 8874260ec..55871f1b1 100644 --- a/axiom/nr_txn.h +++ b/axiom/nr_txn.h @@ -132,6 +132,8 @@ typedef struct _nrtxnopt_t { size_t log_events_max_samples_stored; /* The maximum number of log events per transaction */ bool log_metrics_enabled; /* Whether log metrics are enabled */ + bool message_tracer_segment_parameters_enabled; /* Determines whether to add + message attr */ } nrtxnopt_t; typedef enum _nrtxnstatus_cross_process_t { diff --git a/axiom/tests/Makefile b/axiom/tests/Makefile index 0a37821bb..3d67984ca 100644 --- a/axiom/tests/Makefile +++ b/axiom/tests/Makefile @@ -173,6 +173,7 @@ TESTS := \ test_segment_children \ test_segment_datastore \ test_segment_external \ + test_segment_message \ test_segment_private \ test_segment_terms \ test_segment_traces \ diff --git a/axiom/tests/test_cmd_txndata.c b/axiom/tests/test_cmd_txndata.c index 1107d9a79..ffb53a6c9 100644 --- a/axiom/tests/test_cmd_txndata.c +++ b/axiom/tests/test_cmd_txndata.c @@ -134,7 +134,8 @@ static void test_encode_errors(void) { "[887788,\"txnname\",\"msg\",\"cls\",{\"stack_trace\":[" "\"stacktrace " "json\"],\"agentAttributes\":{\"agent_long\":2},\"userAttributes\":{" - "\"user_long\":1},\"intrinsics\":{\"a\":\"b\",\"guid\":\"abcdef\"}},\"abcdef\"]"), + "\"user_long\":1},\"intrinsics\":{\"a\":\"b\",\"guid\":\"abcdef\"}}," + "\"abcdef\"]"), nr_flatbuffers_table_read_bytes(&tbl, ERROR_FIELD_DATA), nr_flatbuffers_table_read_vector_len(&tbl, ERROR_FIELD_DATA), __FILE__, __LINE__); @@ -1042,6 +1043,7 @@ static void test_encode_txn_event(void) { nrm_add(txn.unscoped_metrics, "Datastore/all", 1 * NR_TIME_DIVISOR); nrm_add(txn.unscoped_metrics, "Datastore/all", 1 * NR_TIME_DIVISOR); nrm_add(txn.unscoped_metrics, "External/all", 2 * NR_TIME_DIVISOR); + nrm_add(txn.unscoped_metrics, "MessageBroker/all", 2 * NR_TIME_DIVISOR); nrm_add(txn.unscoped_metrics, "WebFrontend/QueueTime", 3 * NR_TIME_DIVISOR); txn.attributes = nr_attributes_create(0); @@ -1093,10 +1095,13 @@ static void test_encode_txn_event(void) { "\"timestamp\":123.00000," "\"duration\":0.98700,\"totalTime\":0.98700,\"nr.apdexPerfZone\":" "\"F\"," - "\"queueDuration\":3.00000,\"externalDuration\":2.00000," + "\"queueDuration\":3.00000," + "\"externalDuration\":2.00000," "\"externalCallCount\":1," "\"databaseDuration\":2.00000," "\"databaseCallCount\":2," + "\"messageDuration\":2.00000," + "\"messageCallCount\":1," "\"error\":false}," "{\"user_long\":1},{\"agent_long\":2}]"), nr_flatbuffers_table_read_bytes(&tbl, EVENT_FIELD_DATA), diff --git a/axiom/tests/test_segment_helpers.h b/axiom/tests/test_segment_helpers.h index 908432798..9ff7f54f7 100644 --- a/axiom/tests/test_segment_helpers.h +++ b/axiom/tests/test_segment_helpers.h @@ -12,6 +12,7 @@ #include "nr_segment.h" #include "nr_segment_datastore.h" #include "nr_segment_external.h" +#include "nr_segment_message.h" #include "tlib_main.h" #include "util_metrics_private.h" #include "nr_limits.h" @@ -287,7 +288,7 @@ static NRUNUSED bool test_segment_end_and_keep(nr_segment_t** segment_ptr) { } /* - * Purpose : Ends an external segment without nulling out the segment pointer. + * Purpose : Ends an external segment without nulling out the segment pointer. * * WARNING : This can only be used safely when the segment priority queue is * disabled. @@ -326,4 +327,24 @@ static NRUNUSED bool test_segment_datastore_end_and_keep( return nr_segment_datastore_end(&segment, params); } +/* + * Purpose : Ends a message segment without nulling out the segment pointer. + * + * WARNING : This can only be used safely when the segment priority queue is + * disabled. + */ +static NRUNUSED bool test_segment_message_end_and_keep( + nr_segment_t** segment_ptr, + nr_segment_message_params_t* params) { + nr_segment_t* segment; + + if (NULL == segment_ptr) { + return false; + } + + segment = *segment_ptr; + + return nr_segment_message_end(&segment, params); +} + #endif /* TEST_SEGMENT_HELPERS_HDR */ diff --git a/axiom/tests/test_segment_message.c b/axiom/tests/test_segment_message.c new file mode 100644 index 000000000..a68403caf --- /dev/null +++ b/axiom/tests/test_segment_message.c @@ -0,0 +1,1010 @@ +/* + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "nr_axiom.h" +#include "nr_header.h" +#include "nr_segment_message.h" +#include "test_segment_helpers.h" +#include "nr_attributes.h" +#include "nr_attributes_private.h" +#include "util_hash.h" +#include "util_memory.h" +#include "util_object.h" +#include "util_reply.h" +#include "util_strings.h" +#include "util_text.h" + +#include "tlib_main.h" + +typedef struct { + const char* test_name; + const char* name; + const char* txn_rollup_metric; + const char* library_metric; + uint32_t num_metrics; + const char* destination_name; + const char* cloud_region; + const char* cloud_account_id; + const char* messaging_system; + const char* cloud_resource_id; + const char* server_address; + const char* aws_operation; +} segment_message_expecteds_t; + +static nr_segment_t* mock_txn_segment(void) { + nrtxn_t* txn = new_txn(0); + + return nr_segment_start(txn, NULL, NULL); +} + +static void test_message_segment(nr_segment_message_params_t* params, + nr_segment_cloud_attrs_t* cloud_attrs, + bool message_attributes_enabled, + segment_message_expecteds_t expecteds) { + uint32_t all = NR_ATTRIBUTE_DESTINATION_ALL; + nrobj_t* obj = NULL; + nr_segment_t* seg = mock_txn_segment(); + nrtxn_t* txn = seg->txn; + seg->txn->options.message_tracer_segment_parameters_enabled + = message_attributes_enabled; + + nr_segment_traces_add_cloud_attributes(seg, cloud_attrs); + /* Check the agent cloud attributes. */ + obj = nr_attributes_agent_to_obj(seg->attributes, all); + tlib_pass_if_str_equal(expecteds.test_name, expecteds.aws_operation, + nro_get_hash_string(obj, NR_ATTR_AWS_OPERATION, 0)); + tlib_pass_if_str_equal( + expecteds.test_name, expecteds.cloud_resource_id, + nro_get_hash_string(obj, NR_ATTR_CLOUD_RESOURCE_ID, 0)); + tlib_pass_if_str_equal(expecteds.test_name, expecteds.cloud_account_id, + nro_get_hash_string(obj, NR_ATTR_CLOUD_ACCOUNT_ID, 0)); + tlib_pass_if_str_equal(expecteds.test_name, expecteds.cloud_region, + nro_get_hash_string(obj, NR_ATTR_CLOUD_REGION, 0)); + nro_delete(obj); + + test_segment_message_end_and_keep(&seg, params); + /* Check the metrics and txn naming. */ + tlib_pass_if_str_equal(expecteds.test_name, expecteds.name, + nr_string_get(seg->txn->trace_strings, seg->name)); + test_txn_metric_created(expecteds.test_name, txn->unscoped_metrics, + expecteds.txn_rollup_metric); + test_txn_metric_created(expecteds.test_name, txn->unscoped_metrics, + expecteds.library_metric); + test_metric_vector_size(seg->metrics, expecteds.num_metrics); + + /* Check the segment settings and typed attributes. */ + tlib_pass_if_true(expecteds.test_name, NR_SEGMENT_MESSAGE == seg->type, + "NR_SEGMENT_MESSAGE"); + tlib_pass_if_str_equal(expecteds.test_name, + seg->typed_attributes->message.destination_name, + expecteds.destination_name); + tlib_pass_if_str_equal(expecteds.test_name, + seg->typed_attributes->message.messaging_system, + expecteds.messaging_system); + tlib_pass_if_str_equal(expecteds.test_name, + seg->typed_attributes->message.server_address, + expecteds.server_address); + nr_txn_destroy(&txn); +} + +static void test_bad_parameters(void) { + nr_segment_t seg_null = {0}; + nr_segment_t* seg_null_ptr; + nr_segment_t* seg = mock_txn_segment(); + nrtxn_t* txn = seg->txn; + nr_segment_message_params_t params = {0}; + + tlib_pass_if_false("bad parameters", nr_segment_message_end(NULL, ¶ms), + "expected false"); + + seg_null_ptr = NULL; + + tlib_pass_if_false("bad parameters", + nr_segment_message_end(&seg_null_ptr, ¶ms), + "expected false"); + + seg_null_ptr = &seg_null; + tlib_pass_if_false("bad parameters", + nr_segment_message_end(&seg_null_ptr, ¶ms), + "expected false"); + + tlib_pass_if_false("bad parameters", nr_segment_message_end(&seg, NULL), + "expected false"); + test_metric_vector_size(seg->metrics, 0); + + nr_txn_destroy(&txn); +} + +static void test_segment_message_destination_type(void) { + /* + * The following values are used to create metrics: + * library + * destination_type + * message_action + * destination_name + */ + /* Test NR_MESSAGE_DESTINATION_TYPE_TEMP_TOPIC destination type */ + test_message_segment( + &(nr_segment_message_params_t){ + .library = "SQS", + .message_action = NR_SPANKIND_PRODUCER, + .destination_type = NR_MESSAGE_DESTINATION_TYPE_TEMP_TOPIC, + .destination_name = "my_queue_or_topic"}, + &(nr_segment_cloud_attrs_t){0}, true /* enable attributes */, + (segment_message_expecteds_t){ + .test_name + = "Test NR_MESSAGE_DESTINATION_TYPE_TEMP_TOPIC destination type", + .name = "MessageBroker/SQS/Topic/Produce/Temp", + .txn_rollup_metric = "MessageBroker/all", + .library_metric = "MessageBroker/SQS/all", + .num_metrics = 1, + .destination_name = "my_queue_or_topic", + .cloud_region = NULL, + .cloud_account_id = NULL, + .messaging_system = NULL, + .cloud_resource_id = NULL, + .server_address = NULL, + .aws_operation = NULL}); + + /* Test NR_MESSAGE_DESTINATION_TYPE_TEMP_QUEUE destination type */ + test_message_segment( + &(nr_segment_message_params_t){ + .library = "SQS", + .message_action = NR_SPANKIND_PRODUCER, + .destination_type = NR_MESSAGE_DESTINATION_TYPE_TEMP_QUEUE, + .destination_name = "my_queue_or_topic"}, + &(nr_segment_cloud_attrs_t){0}, true /* enable attributes */, + (segment_message_expecteds_t){ + .test_name + = "Test NR_MESSAGE_DESTINATION_TYPE_TEMP_QUEUE destination type", + .name = "MessageBroker/SQS/Queue/Produce/Temp", + .txn_rollup_metric = "MessageBroker/all", + .library_metric = "MessageBroker/SQS/all", + .num_metrics = 1, + .destination_name = "my_queue_or_topic", + .cloud_region = NULL, + .cloud_account_id = NULL, + .messaging_system = NULL, + .cloud_resource_id = NULL, + .server_address = NULL, + .aws_operation = NULL}); + + /* Test NR_MESSAGE_DESTINATION_TYPE_EXCHANGE destination type */ + test_message_segment( + &(nr_segment_message_params_t){ + .library = "SQS", + .message_action = NR_SPANKIND_PRODUCER, + .destination_type = NR_MESSAGE_DESTINATION_TYPE_EXCHANGE, + .destination_name = "my_queue_or_topic"}, + &(nr_segment_cloud_attrs_t){0}, true /* enable attributes */, + (segment_message_expecteds_t){ + .test_name + = "Test NR_MESSAGE_DESTINATION_TYPE_EXCHANGE destination type", + .name = "MessageBroker/SQS/Exchange/Produce/Named/my_queue_or_topic", + .txn_rollup_metric = "MessageBroker/all", + .library_metric = "MessageBroker/SQS/all", + .num_metrics = 1, + .destination_name = "my_queue_or_topic", + .cloud_region = NULL, + .cloud_account_id = NULL, + .messaging_system = NULL, + .cloud_resource_id = NULL, + .server_address = NULL, + .aws_operation = NULL}); + + /* Test NR_MESSAGE_DESTINATION_TYPE_TOPIC destination type */ + test_message_segment( + &(nr_segment_message_params_t){ + .library = "SQS", + .message_action = NR_SPANKIND_PRODUCER, + .destination_type = NR_MESSAGE_DESTINATION_TYPE_TOPIC, + .destination_name = "my_queue_or_topic"}, + &(nr_segment_cloud_attrs_t){0}, true /* enable attributes */, + (segment_message_expecteds_t){ + .test_name + = "Test NR_MESSAGE_DESTINATION_TYPE_EXCHANGE destination type", + .name = "MessageBroker/SQS/Topic/Produce/Named/my_queue_or_topic", + .txn_rollup_metric = "MessageBroker/all", + .library_metric = "MessageBroker/SQS/all", + .num_metrics = 1, + .destination_name = "my_queue_or_topic", + .cloud_region = NULL, + .cloud_account_id = NULL, + .messaging_system = NULL, + .cloud_resource_id = NULL, + .server_address = NULL, + .aws_operation = NULL}); + + /* Test NR_MESSAGE_DESTINATION_TYPE_QUEUE destination type */ + test_message_segment( + &(nr_segment_message_params_t){ + .library = "SQS", + .message_action = NR_SPANKIND_PRODUCER, + .destination_type = NR_MESSAGE_DESTINATION_TYPE_QUEUE, + .destination_name = "my_queue_or_topic"}, + &(nr_segment_cloud_attrs_t){0}, true /* enable attributes */, + (segment_message_expecteds_t){ + .test_name + = "Test NR_MESSAGE_DESTINATION_TYPE_QUEUE destination type", + .name = "MessageBroker/SQS/Queue/Produce/Named/my_queue_or_topic", + .txn_rollup_metric = "MessageBroker/all", + .library_metric = "MessageBroker/SQS/all", + .num_metrics = 1, + .destination_name = "my_queue_or_topic", + .cloud_region = NULL, + .cloud_account_id = NULL, + .messaging_system = NULL, + .cloud_resource_id = NULL, + .server_address = NULL, + .aws_operation = NULL}); +} + +static void test_segment_message_message_action(void) { + /* + * The following values are used to create metrics: + * library + * destination_type + * message_action + * destination_name + */ + + /* Test NR_SPANKIND_PRODUCER message action */ + test_message_segment( + &(nr_segment_message_params_t){ + .library = "SQS", + .message_action = NR_SPANKIND_PRODUCER, + .destination_type = NR_MESSAGE_DESTINATION_TYPE_TOPIC, + .destination_name = "my_queue_or_topic"}, + &(nr_segment_cloud_attrs_t){0}, true /* enable attributes */, + (segment_message_expecteds_t){ + .test_name = "Test NR_SPANKIND_PRODUCER message action", + .name = "MessageBroker/SQS/Topic/Produce/Named/my_queue_or_topic", + .txn_rollup_metric = "MessageBroker/all", + .library_metric = "MessageBroker/SQS/all", + .num_metrics = 1, + .destination_name = "my_queue_or_topic", + .cloud_region = NULL, + .cloud_account_id = NULL, + .messaging_system = NULL, + .cloud_resource_id = NULL, + .server_address = NULL, + .aws_operation = NULL}); + + /* Test NR_SPANKIND_CONSUMER message action */ + + test_message_segment( + &(nr_segment_message_params_t){ + .library = "SQS", + .message_action = NR_SPANKIND_CONSUMER, + .destination_type = NR_MESSAGE_DESTINATION_TYPE_TOPIC, + .destination_name = "my_queue_or_topic"}, + &(nr_segment_cloud_attrs_t){0}, true /* enable attributes */, + (segment_message_expecteds_t){ + .test_name = "Test NR_SPANKIND_CONSUMER message action", + .name = "MessageBroker/SQS/Topic/Consume/Named/my_queue_or_topic", + .txn_rollup_metric = "MessageBroker/all", + .library_metric = "MessageBroker/SQS/all", + .num_metrics = 1, + .destination_name = "my_queue_or_topic", + .cloud_region = NULL, + .cloud_account_id = NULL, + .messaging_system = NULL, + .cloud_resource_id = NULL, + .server_address = NULL, + .aws_operation = NULL}); + + /* + * Test NR_SPANKIND_CLIENT message action; this is not + * allowed for message segments, should show unknown. + */ + test_message_segment( + &(nr_segment_message_params_t){ + .library = "SQS", + .message_action = NR_SPANKIND_CLIENT, + .destination_type = NR_MESSAGE_DESTINATION_TYPE_TOPIC, + .destination_name = "my_queue_or_topic"}, + &(nr_segment_cloud_attrs_t){0}, true /* enable attributes */, + (segment_message_expecteds_t){ + .test_name = "Test NR_SPANKIND_CLIENT message action", + .name = "MessageBroker/SQS/Topic//Named/my_queue_or_topic", + .txn_rollup_metric = "MessageBroker/all", + .library_metric = "MessageBroker/SQS/all", + .num_metrics = 1, + .destination_name = "my_queue_or_topic", + .cloud_region = NULL, + .cloud_account_id = NULL, + .messaging_system = NULL, + .cloud_resource_id = NULL, + .server_address = NULL, + .aws_operation = NULL}); +} + +static void test_segment_message_library(void) { + /* + * The following values are used to create metrics: + * library + * destination_type + * message_action + * destination_name + */ + /* Test null library */ + test_message_segment( + &(nr_segment_message_params_t){ + .library = NULL, + .message_action = NR_SPANKIND_PRODUCER, + .destination_type = NR_MESSAGE_DESTINATION_TYPE_TOPIC, + .destination_name = "my_queue_or_topic"}, + &(nr_segment_cloud_attrs_t){0}, true /* enable attributes */, + (segment_message_expecteds_t){ + .test_name = "Test null library", + .name + = "MessageBroker//Topic/Produce/Named/my_queue_or_topic", + .txn_rollup_metric = "MessageBroker/all", + .library_metric = "MessageBroker//all", + .num_metrics = 1, + .destination_name = "my_queue_or_topic", + .cloud_region = NULL, + .cloud_account_id = NULL, + .messaging_system = NULL, + .cloud_resource_id = NULL, + .server_address = NULL, + .aws_operation = NULL}); + + /* Test empty library */ + + test_message_segment( + &(nr_segment_message_params_t){ + .library = "", + .message_action = NR_SPANKIND_PRODUCER, + .destination_type = NR_MESSAGE_DESTINATION_TYPE_TOPIC, + .destination_name = "my_queue_or_topic"}, + &(nr_segment_cloud_attrs_t){0}, true /* enable attributes */, + (segment_message_expecteds_t){ + .test_name = "Test empty library", + .name + = "MessageBroker//Topic/Produce/Named/my_queue_or_topic", + .txn_rollup_metric = "MessageBroker/all", + .library_metric = "MessageBroker//all", + .num_metrics = 1, + .destination_name = "my_queue_or_topic", + .cloud_region = NULL, + .cloud_account_id = NULL, + .messaging_system = NULL, + .cloud_resource_id = NULL, + .server_address = NULL, + .aws_operation = NULL}); + + /* Test valid library */ + + test_message_segment( + &(nr_segment_message_params_t){ + .library = "SQS", + .message_action = NR_SPANKIND_PRODUCER, + .destination_type = NR_MESSAGE_DESTINATION_TYPE_TOPIC, + .destination_name = "my_queue_or_topic"}, + &(nr_segment_cloud_attrs_t){0}, true /* enable attributes */, + (segment_message_expecteds_t){ + .test_name = "Test valid library", + .name = "MessageBroker/SQS/Topic/Produce/Named/my_queue_or_topic", + .txn_rollup_metric = "MessageBroker/all", + .library_metric = "MessageBroker/SQS/all", + .num_metrics = 1, + .destination_name = "my_queue_or_topic", + .cloud_region = NULL, + .cloud_account_id = NULL, + .messaging_system = NULL, + .cloud_resource_id = NULL, + .server_address = NULL, + .aws_operation = NULL}); +} + +static void test_segment_message_destination_name(void) { + /* + * The following values are used to create metrics: + * library + * destination_type + * message_action + * destination_name + */ + /* Test null destination_name */ + test_message_segment( + &(nr_segment_message_params_t){ + .library = "SQS", + .message_action = NR_SPANKIND_PRODUCER, + .destination_type = NR_MESSAGE_DESTINATION_TYPE_TOPIC, + .destination_name = NULL}, + &(nr_segment_cloud_attrs_t){0}, true /* enable attributes */, + (segment_message_expecteds_t){ + .test_name = "Test null destination_name", + .name = "MessageBroker/SQS/Topic/Produce/Named/", + .txn_rollup_metric = "MessageBroker/all", + .library_metric = "MessageBroker/SQS/all", + .num_metrics = 1, + .destination_name = NULL, + .cloud_region = NULL, + .cloud_account_id = NULL, + .messaging_system = NULL, + .cloud_resource_id = NULL, + .server_address = NULL, + .aws_operation = NULL}); + + /* Test empty destination_name */ + test_message_segment( + &(nr_segment_message_params_t){ + .library = "SQS", + .message_action = NR_SPANKIND_PRODUCER, + .destination_type = NR_MESSAGE_DESTINATION_TYPE_TOPIC, + .destination_name = ""}, + &(nr_segment_cloud_attrs_t){0}, true /* enable attributes */, + (segment_message_expecteds_t){ + .test_name = "Test empty destination_name", + .name = "MessageBroker/SQS/Topic/Produce/Named/", + .txn_rollup_metric = "MessageBroker/all", + .library_metric = "MessageBroker/SQS/all", + .num_metrics = 1, + .destination_name = NULL, + .cloud_region = NULL, + .cloud_account_id = NULL, + .messaging_system = NULL, + .cloud_resource_id = NULL, + .server_address = NULL, + .aws_operation = NULL}); + + /* Test valid destination_name */ + test_message_segment( + &(nr_segment_message_params_t){ + .library = "SQS", + .message_action = NR_SPANKIND_PRODUCER, + .destination_type = NR_MESSAGE_DESTINATION_TYPE_TOPIC, + .destination_name = "my_destination"}, + &(nr_segment_cloud_attrs_t){0}, true /* enable attributes */, + (segment_message_expecteds_t){ + .test_name = "Test valid destination_name", + .name = "MessageBroker/SQS/Topic/Produce/Named/my_destination", + .txn_rollup_metric = "MessageBroker/all", + .library_metric = "MessageBroker/SQS/all", + .num_metrics = 1, + .destination_name = "my_destination", + .cloud_region = NULL, + .cloud_account_id = NULL, + .messaging_system = NULL, + .cloud_resource_id = NULL, + .server_address = NULL, + .aws_operation = NULL}); +} + +static void test_segment_message_cloud_region(void) { + /* + * cloud_region values should NOT impact the creation of + * metrics. + */ + + /* Test null cloud_region */ + test_message_segment( + &(nr_segment_message_params_t){ + .library = "SQS", + .message_action = NR_SPANKIND_PRODUCER, + .destination_type = NR_MESSAGE_DESTINATION_TYPE_TOPIC, + .destination_name = "my_destination"}, + &(nr_segment_cloud_attrs_t){0}, true /* enable attributes */, + (segment_message_expecteds_t){ + .test_name = "Test null cloud_region", + .name = "MessageBroker/SQS/Topic/Produce/Named/my_destination", + .txn_rollup_metric = "MessageBroker/all", + .library_metric = "MessageBroker/SQS/all", + .num_metrics = 1, + .destination_name = "my_destination", + .cloud_region = NULL, + .cloud_account_id = NULL, + .messaging_system = NULL, + .cloud_resource_id = NULL, + .server_address = NULL, + .aws_operation = NULL}); + + /* Test empty cloud_region */ + test_message_segment( + &(nr_segment_message_params_t){ + .library = "SQS", + .message_action = NR_SPANKIND_PRODUCER, + .destination_type = NR_MESSAGE_DESTINATION_TYPE_TOPIC, + .destination_name = "my_destination"}, + &(nr_segment_cloud_attrs_t){.cloud_region = ""}, + true /* enable attributes */, + (segment_message_expecteds_t){ + .test_name = "Test empty cloud_region", + .name = "MessageBroker/SQS/Topic/Produce/Named/my_destination", + .txn_rollup_metric = "MessageBroker/all", + .library_metric = "MessageBroker/SQS/all", + .num_metrics = 1, + .destination_name = "my_destination", + .cloud_region = NULL, + .cloud_account_id = NULL, + .messaging_system = NULL, + .cloud_resource_id = NULL, + .server_address = NULL, + .aws_operation = NULL}); + + /* Test valid cloud_region */ + test_message_segment( + &(nr_segment_message_params_t){ + .library = "SQS", + .message_action = NR_SPANKIND_PRODUCER, + .destination_type = NR_MESSAGE_DESTINATION_TYPE_TOPIC, + .destination_name = "my_destination"}, + &(nr_segment_cloud_attrs_t){.cloud_region = "wild-west-1"}, + true /* enable attributes */, + (segment_message_expecteds_t){ + .test_name = "Test valid cloud_region", + .name = "MessageBroker/SQS/Topic/Produce/Named/my_destination", + .txn_rollup_metric = "MessageBroker/all", + .library_metric = "MessageBroker/SQS/all", + .num_metrics = 1, + .destination_name = "my_destination", + .cloud_region = "wild-west-1", + .cloud_account_id = NULL, + .messaging_system = NULL, + .cloud_resource_id = NULL, + .server_address = NULL, + .aws_operation = NULL}); +} + +static void test_segment_message_cloud_account_id(void) { + /* + * cloud_account_id values should NOT impact the creation + * of metrics. + */ + + /* Test null cloud_account_id */ + test_message_segment( + &(nr_segment_message_params_t){ + .library = "SQS", + .message_action = NR_SPANKIND_PRODUCER, + .destination_type = NR_MESSAGE_DESTINATION_TYPE_TOPIC, + .destination_name = "my_destination"}, + &(nr_segment_cloud_attrs_t){0}, true /* enable attributes */, + (segment_message_expecteds_t){ + .test_name = "Test null cloud_account_id", + .name = "MessageBroker/SQS/Topic/Produce/Named/my_destination", + .txn_rollup_metric = "MessageBroker/all", + .library_metric = "MessageBroker/SQS/all", + .num_metrics = 1, + .destination_name = "my_destination", + .cloud_region = NULL, + .cloud_account_id = NULL, + .messaging_system = NULL, + .cloud_resource_id = NULL, + .server_address = NULL, + .aws_operation = NULL}); + + /* Test empty cloud_account_id */ + test_message_segment( + &(nr_segment_message_params_t){ + .library = "SQS", + .message_action = NR_SPANKIND_PRODUCER, + .destination_type = NR_MESSAGE_DESTINATION_TYPE_TOPIC, + .destination_name = "my_destination"}, + &(nr_segment_cloud_attrs_t){.cloud_account_id = ""}, + true /* enable attributes */, + (segment_message_expecteds_t){ + .test_name = "Test empty cloud_account_id", + .name = "MessageBroker/SQS/Topic/Produce/Named/my_destination", + .txn_rollup_metric = "MessageBroker/all", + .library_metric = "MessageBroker/SQS/all", + .num_metrics = 1, + .destination_name = "my_destination", + .cloud_region = NULL, + .cloud_account_id = NULL, + .messaging_system = NULL, + .cloud_resource_id = NULL, + .server_address = NULL, + .aws_operation = NULL}); + + /* Test valid cloud_account_id */ + test_message_segment( + &(nr_segment_message_params_t){ + .library = "SQS", + .message_action = NR_SPANKIND_PRODUCER, + .destination_type = NR_MESSAGE_DESTINATION_TYPE_TOPIC, + .destination_name = "my_destination"}, + &(nr_segment_cloud_attrs_t){.cloud_account_id = "12345678"}, + true /* enable attributes */, + (segment_message_expecteds_t){ + .test_name = "Test valid cloud_account_id", + .name = "MessageBroker/SQS/Topic/Produce/Named/my_destination", + .txn_rollup_metric = "MessageBroker/all", + .library_metric = "MessageBroker/SQS/all", + .num_metrics = 1, + .destination_name = "my_destination", + .cloud_region = NULL, + .cloud_account_id = "12345678", + .messaging_system = NULL, + .cloud_resource_id = NULL, + .server_address = NULL, + .aws_operation = NULL}); +} + +static void test_segment_message_messaging_system(void) { + /* + * messaging_system values should NOT impact the creation + * of metrics. + */ + + /* Test null messaging_system */ + test_message_segment( + &(nr_segment_message_params_t){ + .messaging_system = NULL, + .library = "SQS", + .message_action = NR_SPANKIND_PRODUCER, + .destination_type = NR_MESSAGE_DESTINATION_TYPE_TOPIC, + .destination_name = "my_destination"}, + &(nr_segment_cloud_attrs_t){0}, true /* enable attributes */, + (segment_message_expecteds_t){ + .test_name = "Test null messaging_system", + .name = "MessageBroker/SQS/Topic/Produce/Named/my_destination", + .txn_rollup_metric = "MessageBroker/all", + .library_metric = "MessageBroker/SQS/all", + .num_metrics = 1, + .destination_name = "my_destination", + .cloud_region = NULL, + .cloud_account_id = NULL, + .messaging_system = NULL, + .cloud_resource_id = NULL, + .server_address = NULL, + .aws_operation = NULL}); + + /* Test empty messaging_system */ + test_message_segment( + &(nr_segment_message_params_t){ + .messaging_system = "", + .library = "SQS", + .message_action = NR_SPANKIND_PRODUCER, + .destination_type = NR_MESSAGE_DESTINATION_TYPE_TOPIC, + .destination_name = "my_destination"}, + &(nr_segment_cloud_attrs_t){0}, true /* enable attributes */, + (segment_message_expecteds_t){ + .test_name = "Test empty messaging_system", + .name = "MessageBroker/SQS/Topic/Produce/Named/my_destination", + .txn_rollup_metric = "MessageBroker/all", + .library_metric = "MessageBroker/SQS/all", + .num_metrics = 1, + .destination_name = "my_destination", + .cloud_region = NULL, + .cloud_account_id = NULL, + .messaging_system = NULL, + .cloud_resource_id = NULL, + .server_address = NULL, + .aws_operation = NULL}); + + /* Test valid messaging_system */ + test_message_segment( + &(nr_segment_message_params_t){ + .messaging_system = "my_messaging_system", + .library = "SQS", + .message_action = NR_SPANKIND_PRODUCER, + .destination_type = NR_MESSAGE_DESTINATION_TYPE_TOPIC, + .destination_name = "my_destination"}, + &(nr_segment_cloud_attrs_t){0}, true /* enable attributes */, + (segment_message_expecteds_t){ + .test_name = "Test valid messaging_system", + .name = "MessageBroker/SQS/Topic/Produce/Named/my_destination", + .txn_rollup_metric = "MessageBroker/all", + .library_metric = "MessageBroker/SQS/all", + .num_metrics = 1, + .destination_name = "my_destination", + .cloud_region = NULL, + .cloud_account_id = NULL, + .messaging_system = "my_messaging_system", + .cloud_resource_id = NULL, + .server_address = NULL, + .aws_operation = NULL}); +} + +static void test_segment_message_cloud_resource_id(void) { + /* + * cloud_resource_id values should NOT impact the creation + * of metrics. + */ + + /* Test null cloud_resource_id */ + test_message_segment( + &(nr_segment_message_params_t){ + .library = "SQS", + .message_action = NR_SPANKIND_PRODUCER, + .destination_type = NR_MESSAGE_DESTINATION_TYPE_TOPIC, + .destination_name = "my_destination"}, + &(nr_segment_cloud_attrs_t){0}, true /* enable attributes */, + (segment_message_expecteds_t){ + .test_name = "Test null cloud_resource_id ", + .name = "MessageBroker/SQS/Topic/Produce/Named/my_destination", + .txn_rollup_metric = "MessageBroker/all", + .library_metric = "MessageBroker/SQS/all", + .num_metrics = 1, + .destination_name = "my_destination", + .cloud_region = NULL, + .cloud_account_id = NULL, + .messaging_system = NULL, + .cloud_resource_id = NULL, + .server_address = NULL, + .aws_operation = NULL}); + + /* Test empty cloud_resource_id */ + test_message_segment( + &(nr_segment_message_params_t){ + .library = "SQS", + .message_action = NR_SPANKIND_PRODUCER, + .destination_type = NR_MESSAGE_DESTINATION_TYPE_TOPIC, + .destination_name = "my_destination"}, + &(nr_segment_cloud_attrs_t){.cloud_resource_id = ""}, + true /* enable attributes */, + (segment_message_expecteds_t){ + .test_name = "Test empty cloud_resource_id ", + .name = "MessageBroker/SQS/Topic/Produce/Named/my_destination", + .txn_rollup_metric = "MessageBroker/all", + .library_metric = "MessageBroker/SQS/all", + .num_metrics = 1, + .destination_name = "my_destination", + .cloud_region = NULL, + .cloud_account_id = NULL, + .messaging_system = NULL, + .cloud_resource_id = NULL, + .server_address = NULL, + .aws_operation = NULL}); + + /* Test valid cloud_resource_id */ + test_message_segment( + &(nr_segment_message_params_t){ + .library = "SQS", + .message_action = NR_SPANKIND_PRODUCER, + .destination_type = NR_MESSAGE_DESTINATION_TYPE_TOPIC, + .destination_name = "my_destination"}, + &(nr_segment_cloud_attrs_t){.cloud_resource_id = "my_resource_id"}, + true /* enable attributes */, + (segment_message_expecteds_t){ + .test_name = "Test valid cloud_resource_id ", + .name = "MessageBroker/SQS/Topic/Produce/Named/my_destination", + .txn_rollup_metric = "MessageBroker/all", + .library_metric = "MessageBroker/SQS/all", + .num_metrics = 1, + .destination_name = "my_destination", + .cloud_region = NULL, + .cloud_account_id = NULL, + .messaging_system = NULL, + .cloud_resource_id = "my_resource_id", + .server_address = NULL, + .aws_operation = NULL}); +} + +static void test_segment_message_server_address(void) { + /* + * server_address values should NOT impact the creation + * of metrics. + */ + + /* Test null server_address */ + test_message_segment( + &(nr_segment_message_params_t){ + .server_address = "localhost", + .library = "SQS", + .message_action = NR_SPANKIND_PRODUCER, + .destination_type = NR_MESSAGE_DESTINATION_TYPE_TOPIC, + .destination_name = "my_destination"}, + &(nr_segment_cloud_attrs_t){0}, true /* enable attributes */, + (segment_message_expecteds_t){ + .test_name = "Test null server_address", + .name = "MessageBroker/SQS/Topic/Produce/Named/my_destination", + .txn_rollup_metric = "MessageBroker/all", + .library_metric = "MessageBroker/SQS/all", + .num_metrics = 1, + .destination_name = "my_destination", + .cloud_region = NULL, + .cloud_account_id = NULL, + .messaging_system = NULL, + .cloud_resource_id = NULL, + .server_address = "localhost", + .aws_operation = NULL}); + + /* Test empty server_address */ + test_message_segment( + &(nr_segment_message_params_t){ + .server_address = "", + .library = "SQS", + .message_action = NR_SPANKIND_PRODUCER, + .destination_type = NR_MESSAGE_DESTINATION_TYPE_TOPIC, + .destination_name = "my_destination"}, + &(nr_segment_cloud_attrs_t){0}, true /* enable attributes */, + (segment_message_expecteds_t){ + .test_name = "Test empty server_address", + .name = "MessageBroker/SQS/Topic/Produce/Named/my_destination", + .txn_rollup_metric = "MessageBroker/all", + .library_metric = "MessageBroker/SQS/all", + .num_metrics = 1, + .destination_name = "my_destination", + .cloud_region = NULL, + .cloud_account_id = NULL, + .messaging_system = NULL, + .cloud_resource_id = NULL, + .server_address = NULL, + .aws_operation = NULL}); + + /* Test valid server_address */ + test_message_segment( + &(nr_segment_message_params_t){ + .server_address = "localhost", + .library = "SQS", + .message_action = NR_SPANKIND_PRODUCER, + .destination_type = NR_MESSAGE_DESTINATION_TYPE_TOPIC, + .destination_name = "my_destination"}, + &(nr_segment_cloud_attrs_t){0}, true /* enable attributes */, + (segment_message_expecteds_t){ + .test_name = "Test valid server_address", + .name = "MessageBroker/SQS/Topic/Produce/Named/my_destination", + .txn_rollup_metric = "MessageBroker/all", + .library_metric = "MessageBroker/SQS/all", + .num_metrics = 1, + .destination_name = "my_destination", + .cloud_region = NULL, + .cloud_account_id = NULL, + .messaging_system = NULL, + .cloud_resource_id = NULL, + .server_address = "localhost", + .aws_operation = NULL}); +} + +static void test_segment_message_aws_operation(void) { + /* + * aws_operation values should NOT impact the creation + * of metrics. + */ + + /* Test null aws_operation */ + test_message_segment( + &(nr_segment_message_params_t){ + .library = "SQS", + .message_action = NR_SPANKIND_PRODUCER, + .destination_type = NR_MESSAGE_DESTINATION_TYPE_TOPIC, + .destination_name = "my_destination"}, + &(nr_segment_cloud_attrs_t){0}, true /* enable attributes */, + (segment_message_expecteds_t){ + .test_name = "Test null aws_operation", + .name = "MessageBroker/SQS/Topic/Produce/Named/my_destination", + .txn_rollup_metric = "MessageBroker/all", + .library_metric = "MessageBroker/SQS/all", + .num_metrics = 1, + .destination_name = "my_destination", + .cloud_region = NULL, + .cloud_account_id = NULL, + .messaging_system = NULL, + .cloud_resource_id = NULL, + .server_address = NULL, + .aws_operation = NULL}); + + /* Test empty aws_operation */ + test_message_segment( + &(nr_segment_message_params_t){ + .library = "SQS", + .message_action = NR_SPANKIND_PRODUCER, + .destination_type = NR_MESSAGE_DESTINATION_TYPE_TOPIC, + .destination_name = "my_destination"}, + &(nr_segment_cloud_attrs_t){.aws_operation = ""}, + true /* enable attributes */, + (segment_message_expecteds_t){ + .test_name = "Test empty aws_operation", + .name = "MessageBroker/SQS/Topic/Produce/Named/my_destination", + .txn_rollup_metric = "MessageBroker/all", + .library_metric = "MessageBroker/SQS/all", + .num_metrics = 1, + .destination_name = "my_destination", + .cloud_region = NULL, + .cloud_account_id = NULL, + .messaging_system = NULL, + .cloud_resource_id = NULL, + .server_address = NULL, + .aws_operation = NULL}); + + /* Test valid aws_operation */ + test_message_segment( + &(nr_segment_message_params_t){ + .library = "SQS", + .message_action = NR_SPANKIND_PRODUCER, + .destination_type = NR_MESSAGE_DESTINATION_TYPE_TOPIC, + .destination_name = "my_destination"}, + &(nr_segment_cloud_attrs_t){.aws_operation = "sendMessage"}, + true /* enable attributes */, + (segment_message_expecteds_t){ + .test_name = "Test valid aws_operation", + .name = "MessageBroker/SQS/Topic/Produce/Named/my_destination", + .txn_rollup_metric = "MessageBroker/all", + .library_metric = "MessageBroker/SQS/all", + .num_metrics = 1, + .destination_name = "my_destination", + .cloud_region = NULL, + .cloud_account_id = NULL, + .messaging_system = NULL, + .cloud_resource_id = NULL, + .server_address = NULL, + .aws_operation = "sendMessage"}); +} + +static void test_segment_message_parameters_enabled(void) { + /* + * Attributes should be set based on value of parameters_enabled. + */ + + /* Test true message_parameters_enabled */ + test_message_segment( + &(nr_segment_message_params_t){ + .server_address = "localhost", + .messaging_system = "my_system", + .library = "SQS", + .message_action = NR_SPANKIND_PRODUCER, + .destination_type = NR_MESSAGE_DESTINATION_TYPE_TOPIC, + .destination_name = "my_destination"}, + &(nr_segment_cloud_attrs_t){.aws_operation = "sendMessage", + .cloud_region = "wild-west-1", + .cloud_account_id = "12345678", + .cloud_resource_id = "my_resource_id"}, + true /* enable attributes */, + (segment_message_expecteds_t){ + .test_name = "Test true message_parameters_enabled", + .name = "MessageBroker/SQS/Topic/Produce/Named/my_destination", + .txn_rollup_metric = "MessageBroker/all", + .library_metric = "MessageBroker/SQS/all", + .num_metrics = 1, + .destination_name = "my_destination", + .cloud_region = "wild-west-1", + .cloud_account_id = "12345678", + .messaging_system = "my_system", + .cloud_resource_id = "my_resource_id", + .server_address = "localhost", + .aws_operation = "sendMessage"}); + + /* + * Test false message_parameters_enabled. Message attributes should not show, + * but cloud attributes should be unaffected. + */ + test_message_segment( + &(nr_segment_message_params_t){ + .server_address = "localhost", + .messaging_system = "my_system", + .library = "SQS", + .message_action = NR_SPANKIND_PRODUCER, + .destination_type = NR_MESSAGE_DESTINATION_TYPE_TOPIC, + .destination_name = "my_destination"}, + &(nr_segment_cloud_attrs_t){.aws_operation = "sendMessage", + .cloud_region = "wild-west-1", + .cloud_account_id = "12345678", + .cloud_resource_id = "my_resource_id"}, + false /* enable attributes */, + (segment_message_expecteds_t){ + .test_name = "Test false message_parameters_enabled", + .name = "MessageBroker/SQS/Topic/Produce/Named/my_destination", + .txn_rollup_metric = "MessageBroker/all", + .library_metric = "MessageBroker/SQS/all", + .num_metrics = 1, + .destination_name = NULL, + .cloud_region = "wild-west-1", + .cloud_account_id = "12345678", + .messaging_system = NULL, + .cloud_resource_id = "my_resource_id", + .server_address = NULL, + .aws_operation = "sendMessage"}); +} + +tlib_parallel_info_t parallel_info = {.suggested_nthreads = 4, .state_size = 0}; + +void test_main(void* p NRUNUSED) { + test_bad_parameters(); + test_segment_message_destination_type(); + test_segment_message_message_action(); + test_segment_message_library(); + test_segment_message_destination_name(); + test_segment_message_cloud_region(); + test_segment_message_cloud_account_id(); + test_segment_message_messaging_system(); + test_segment_message_cloud_resource_id(); + test_segment_message_server_address(); + test_segment_message_aws_operation(); + test_segment_message_parameters_enabled(); +} diff --git a/axiom/tests/test_segment_private.c b/axiom/tests/test_segment_private.c index e586f1f4f..82e0dfd80 100644 --- a/axiom/tests/test_segment_private.c +++ b/axiom/tests/test_segment_private.c @@ -25,6 +25,7 @@ static void test_bad_parameters(void) { nr_segment_destroy_fields(NULL); nr_segment_datastore_destroy_fields(NULL); nr_segment_external_destroy_fields(NULL); + nr_segment_message_destroy_fields(NULL); nr_segment_metric_destroy_fields(NULL); nr_segment_error_destroy_fields(NULL); } @@ -205,7 +206,6 @@ static void test_set_custom(void) { tlib_pass_if_int_equal( "Setting an untyped segment to custom must set the type", (int)NR_SEGMENT_CUSTOM, (int)s.type); - nr_segment_set_datastore(&t, &d); tlib_pass_if_true("Setting a datastore segment to custom must be successful", nr_segment_set_custom(&t), "Expected true"); @@ -316,6 +316,48 @@ static void test_set_destroy_external_fields(void) { &s.typed_attributes); } +static void test_set_destroy_message_fields(void) { + nr_segment_t s = {.type = NR_SEGMENT_MESSAGE}; + + nr_segment_message_t m = {.message_action = NR_SPANKIND_CLIENT, + .messaging_system = "my_messaging_system", + .server_address = "localhost"}; + + nr_segment_external_t e = {.transaction_guid = "transaction_guid", + .uri = "uri", + .library = "library", + .procedure = "procedure", + .status = 200}; + /* + * Test : Bad parameters. + */ + tlib_pass_if_false( + "Setting a NULL segment's message attributes must not be successful", + nr_segment_set_message(NULL, &m), "Expected false"); + + tlib_pass_if_false( + "Setting a segment with NULL message attributes must not be successful", + nr_segment_set_message(&s, NULL), "Expected false"); + + /* + * Test : Normal operation. + */ + tlib_pass_if_true("Setting a segment's message attributes must be successful", + nr_segment_set_message(&s, &m), "Expected true"); + + tlib_pass_if_true( + "Setting a segment from message attributes to external attributes must " + "be successful", + nr_segment_set_external(&s, &e), "Expected true"); + + /* Valgrind shall affirm that the attributes for s were cleaned + * up when the segment type was changed from message to external. + */ + + /* Clean up */ + nr_segment_destroy_typed_attributes(NR_SEGMENT_EXTERNAL, &s.typed_attributes); +} + static void test_destroy_typed_attributes(void) { nr_segment_t s = {0}; char* test_string = "0123456789"; @@ -325,10 +367,41 @@ static void test_destroy_typed_attributes(void) { */ nr_segment_destroy_typed_attributes(NR_SEGMENT_EXTERNAL, NULL); nr_segment_destroy_typed_attributes(NR_SEGMENT_EXTERNAL, &s.typed_attributes); + tlib_pass_if_null( + "Even with bad parameters, nr_segment_destroy_typed_attributes should " + "not crash and s.typed_attributes should be NULL", + s.typed_attributes); nr_segment_destroy_typed_attributes(NR_SEGMENT_DATASTORE, NULL); nr_segment_destroy_typed_attributes(NR_SEGMENT_DATASTORE, &s.typed_attributes); + tlib_pass_if_null( + "Even with bad parameters, nr_segment_destroy_typed_attributes should " + "not crash and s.typed_attributes should be NULL", + s.typed_attributes); + nr_segment_destroy_typed_attributes(NR_SEGMENT_MESSAGE, NULL); + nr_segment_destroy_typed_attributes(NR_SEGMENT_MESSAGE, &s.typed_attributes); + tlib_pass_if_null( + "Even with bad parameters, nr_segment_destroy_typed_attributes should " + "not crash and s.typed_attributes should be NULL", + s.typed_attributes); + /* + * Test : Clean up typed attributes for a message segment + */ + s.type = NR_SEGMENT_MESSAGE; + s.typed_attributes = nr_zalloc(sizeof(nr_segment_typed_attributes_t)); + s.typed_attributes->message.destination_name = nr_strdup("queue_name"); + s.typed_attributes->message.messaging_system = nr_strdup("aws_sqs"); + s.typed_attributes->message.server_address = nr_strdup("localhost"); + + /* + * Valgrind shall affirm that the attributes were cleaned up. + */ + nr_segment_destroy_typed_attributes(NR_SEGMENT_MESSAGE, &s.typed_attributes); + tlib_pass_if_null( + "After nr_segment_destroy_typed_attributes, s.typed_attributes should be " + "NULL", + s.typed_attributes); /* * Test : Clean up typed attributes for an external segment */ @@ -340,7 +413,14 @@ static void test_destroy_typed_attributes(void) { s.typed_attributes->external.procedure = nr_strdup(test_string); s.typed_attributes->external.status = 200; + /* + * Valgrind shall affirm that the attributes were cleaned up. + */ nr_segment_destroy_typed_attributes(NR_SEGMENT_EXTERNAL, &s.typed_attributes); + tlib_pass_if_null( + "After nr_segment_destroy_typed_attributes, s.typed_attributes should be " + "NULL", + s.typed_attributes); /* * Test : Clean up typed attributes for a datastore segment @@ -358,8 +438,15 @@ static void test_destroy_typed_attributes(void) { = nr_strdup(test_string); s.typed_attributes->datastore.instance.database_name = nr_strdup(test_string); + /* + * Valgrind shall affirm that the attributes were cleaned up. + */ nr_segment_destroy_typed_attributes(NR_SEGMENT_DATASTORE, &s.typed_attributes); + tlib_pass_if_null( + "After nr_segment_destroy_typed_attributes, s.typed_attributes should be " + "NULL", + s.typed_attributes); } static void test_destroy_fields(void) { @@ -394,6 +481,7 @@ void test_main(void* p NRUNUSED) { test_set_custom(); test_set_destroy_datastore_fields(); test_set_destroy_external_fields(); + test_set_destroy_message_fields(); test_destroy_typed_attributes(); test_destroy_fields(); test_destroy_metric(); diff --git a/axiom/tests/test_segment_traces.c b/axiom/tests/test_segment_traces.c index 450ea8af6..b43e15b28 100644 --- a/axiom/tests/test_segment_traces.c +++ b/axiom/tests/test_segment_traces.c @@ -51,6 +51,10 @@ tlib_pass_if_str_equal("category", "http", \ nr_span_event_get_category(evt)); \ break; \ + case NR_SPAN_MESSAGE: \ + tlib_pass_if_str_equal("category", "message", \ + nr_span_event_get_category(evt)); \ + break; \ default: \ tlib_pass_if_true("invalid category", false, "category=%s", \ nr_span_event_get_category(evt)); \ @@ -86,6 +90,20 @@ tlib_pass_if_int_equal("status", expected_status, \ nr_span_event_get_external_status(span_event)); +#define SPAN_EVENT_COMPARE_MESSAGE(span_event, expected_destination_name, \ + expected_messaging_system, \ + expected_server_address) \ + tlib_pass_if_str_equal("messaging.destination.name", \ + expected_destination_name, \ + nr_span_event_get_message( \ + span_event, NR_SPAN_MESSAGE_DESTINATION_NAME)); \ + tlib_pass_if_str_equal("messaging.system", expected_messaging_system, \ + nr_span_event_get_message( \ + span_event, NR_SPAN_MESSAGE_MESSAGING_SYSTEM)); \ + tlib_pass_if_str_equal( \ + "server.address", expected_server_address, \ + nr_span_event_get_message(span_event, NR_SPAN_MESSAGE_SERVER_ADDRESS)); + static void nr_vector_span_event_dtor(void* element, void* userdata NRUNUSED) { nr_span_event_destroy((nr_span_event_t**)&element); } @@ -652,13 +670,15 @@ static void test_json_print_segments_invalid_typed_attributes(void) { nr_span_event_t* evt_root; nr_span_event_t* evt_a; nr_span_event_t* evt_b; + nr_span_event_t* evt_c; nrtxn_t txn = {0}; // clang-format off - nr_segment_t root = {.txn = &txn, .start_time = 0, .stop_time = 9000}; + nr_segment_t root = {.txn = &txn, .start_time = 0, .stop_time = 11000}; nr_segment_t A = {.txn = &txn, .start_time = 1000, .stop_time = 6000}; nr_segment_t B = {.txn = &txn, .start_time = 6000, .stop_time = 8000}; + nr_segment_t C = {.txn = &txn, .start_time = 9000, .stop_time = 10000}; // clang-format on buf = nr_buffer_create(4096, 4096); @@ -668,20 +688,23 @@ static void test_json_print_segments_invalid_typed_attributes(void) { /* Mock up the transaction */ mock_txn(&txn, &root); txn.abs_start_time = 1000; - txn.segment_count = 2; + txn.segment_count = 3; /* Create a collection of mock segments */ nr_segment_children_init(&root.children); nr_segment_add_child(&root, &A); nr_segment_add_child(&root, &B); + nr_segment_add_child(&root, &C); root.name = nr_string_add(txn.trace_strings, "WebTransaction/*"); A.name = nr_string_add(txn.trace_strings, "A"); B.name = nr_string_add(txn.trace_strings, "B"); + C.name = nr_string_add(txn.trace_strings, "C"); A.type = NR_SEGMENT_EXTERNAL; B.type = NR_SEGMENT_DATASTORE; + C.type = NR_SEGMENT_MESSAGE; /* * Test : Normal operation @@ -689,23 +712,27 @@ static void test_json_print_segments_invalid_typed_attributes(void) { rv = nr_segment_traces_json_print_segments(buf, span_events, NULL, NULL, &txn, &root, segment_names); tlib_pass_if_bool_equal("success", true, rv); - test_buffer_contents("datastore params", buf, - "[0,9,\"`0\",{},[[1,6," - "\"`1\",{},[]],[6,8," - "\"`2\",{},[]]]]"); + test_buffer_contents("segment attributes", buf, + "[0,11,\"`0\",{}," + "[[1,6,\"`1\",{},[]]," + "[6,8,\"`2\",{},[]]," + "[9,10,\"`3\",{},[]]]]"); - tlib_pass_if_uint_equal("span event size", nr_vector_size(span_events), 3); + tlib_pass_if_uint_equal("span event size", nr_vector_size(span_events), 4); evt_root = (nr_span_event_t*)nr_vector_get(span_events, 0); evt_a = (nr_span_event_t*)nr_vector_get(span_events, 1); evt_b = (nr_span_event_t*)nr_vector_get(span_events, 2); + evt_c = (nr_span_event_t*)nr_vector_get(span_events, 3); SPAN_EVENT_COMPARE(evt_root, "WebTransaction/*", NR_SPAN_GENERIC, NULL, 1000, - 9000); + 11000); SPAN_EVENT_COMPARE(evt_a, "A", NR_SPAN_HTTP, evt_root, 2000, 5000); SPAN_EVENT_COMPARE_EXTERNAL(evt_a, NULL, NULL, NULL, 0); SPAN_EVENT_COMPARE(evt_b, "B", NR_SPAN_DATASTORE, evt_root, 7000, 2000); SPAN_EVENT_COMPARE_DATASTORE(evt_b, NULL, NULL, NULL, NULL); + SPAN_EVENT_COMPARE(evt_c, "C", NR_SPAN_MESSAGE, evt_root, 10000, 1000); + SPAN_EVENT_COMPARE_MESSAGE(evt_c, NULL, NULL, NULL); /* Clean up */ nr_segment_children_deinit(&root.children); @@ -713,6 +740,7 @@ static void test_json_print_segments_invalid_typed_attributes(void) { nr_segment_destroy_fields(&A); nr_segment_destroy_fields(&B); + nr_segment_destroy_fields(&C); cleanup_mock_txn(&txn); nr_string_pool_destroy(&segment_names); @@ -904,7 +932,88 @@ static void test_json_print_segments_external_async_user_attrs(void) { nr_vector_destroy(&span_events); } -static void test_json_print_segments_datastore_external(void) { +static void test_json_print_segments_message_attributes(void) { + bool rv; + nrbuf_t* buf; + nr_vector_t* span_events; + nrpool_t* segment_names; + + nrtxn_t txn = {0}; + + nr_span_event_t* evt_root; + nr_span_event_t* evt_a; + + // clang-format off + nr_segment_t root = {.txn = &txn, .start_time = 0, .stop_time = 9000}; + nr_segment_t A = {.txn = &txn, .start_time = 1000, .stop_time = 6000}; + // clang-format on + + buf = nr_buffer_create(4096, 4096); + span_events = nr_vector_create(9, nr_vector_span_event_dtor, NULL); + segment_names = nr_string_pool_create(); + + /* Mock up the transaction */ + mock_txn(&txn, &root); + txn.abs_start_time = 1000; + txn.segment_count = 2; + + /* Create a collection of mock segments */ + + /* ------root------- + * ------A------ + */ + + nr_segment_children_init(&root.children); + + nr_segment_add_child(&root, &A); + + root.name = nr_string_add(txn.trace_strings, "WebTransaction/*"); + A.name = nr_string_add(txn.trace_strings, "A"); + + A.type = NR_SEGMENT_MESSAGE; + A.attributes = NULL; + A.typed_attributes = nr_zalloc(sizeof(nr_segment_typed_attributes_t)); + A.typed_attributes->message.destination_name = nr_strdup("queue_name"); + A.typed_attributes->message.messaging_system = nr_strdup("aws_sqs"); + A.typed_attributes->message.server_address = nr_strdup("localhost"); + + /* + * Test : Normal operation + */ + rv = nr_segment_traces_json_print_segments(buf, span_events, NULL, NULL, &txn, + &root, segment_names); + tlib_pass_if_bool_equal("success", true, rv); + test_buffer_contents("message attributes", buf, + "[0,9,\"`0\",{},[[1,6,\"`1\",{" + "\"destination_name\":\"queue_name\"," + "\"messaging_system\":\"aws_sqs\"," + "\"server_address\":\"localhost\"" + "},[]]]]"); + + tlib_pass_if_uint_equal("span event size", nr_vector_size(span_events), 2); + + evt_root = (nr_span_event_t*)nr_vector_get(span_events, 0); + evt_a = (nr_span_event_t*)nr_vector_get(span_events, 1); + + SPAN_EVENT_COMPARE(evt_root, "WebTransaction/*", NR_SPAN_GENERIC, NULL, 1000, + 9000); + SPAN_EVENT_COMPARE(evt_a, "A", NR_SPAN_MESSAGE, evt_root, 2000, 5000); + SPAN_EVENT_COMPARE_MESSAGE(evt_a, "queue_name", "aws_sqs", "localhost"); + + /* Clean up */ + nr_segment_children_deinit(&root.children); + nr_segment_destroy_fields(&root); + + nr_segment_destroy_fields(&A); + + cleanup_mock_txn(&txn); + nr_string_pool_destroy(&segment_names); + + nr_buffer_destroy(&buf); + nr_vector_destroy(&span_events); +} + +static void test_json_print_segments_datastore_external_message(void) { bool rv; nrbuf_t* buf; nr_vector_t* span_events; @@ -972,12 +1081,11 @@ static void test_json_print_segments_datastore_external(void) { C.typed_attributes->external.transaction_guid = nr_strdup("guid"); C.typed_attributes->external.status = 200; - D.type = NR_SEGMENT_DATASTORE; + D.type = NR_SEGMENT_MESSAGE; D.attributes = NULL; D.typed_attributes = nr_zalloc(sizeof(nr_segment_typed_attributes_t)); - D.typed_attributes->datastore.sql = nr_strdup("SELECT pass"); - D.typed_attributes->datastore.instance.host = nr_strdup("localhost"); - D.typed_attributes->datastore.instance.database_name = nr_strdup("db"); + D.typed_attributes->message.destination_name = nr_strdup("queue_name"); + D.typed_attributes->message.messaging_system = nr_strdup("aws_sqs"); /* * Test : Normal operation @@ -999,9 +1107,8 @@ static void test_json_print_segments_datastore_external(void) { "\"transaction_guid\":\"guid\"," "\"status\":200},[]]," "[5,6,\"`4\"," - "{\"host\":\"localhost\"," - "\"database_name\":\"db\"," - "\"sql\":\"SELECT pass\"},[]]]]]]"); + "{\"destination_name\":\"queue_name\"," + "\"messaging_system\":\"aws_sqs\"},[]]]]]]"); tlib_pass_if_uint_equal("span event size", nr_vector_size(span_events), 5); @@ -1019,9 +1126,8 @@ static void test_json_print_segments_datastore_external(void) { "localhost:3308"); SPAN_EVENT_COMPARE(evt_c, "C", NR_SPAN_HTTP, evt_a, 5000, 1000); SPAN_EVENT_COMPARE_EXTERNAL(evt_c, "example.com", "GET", "curl", 200); - SPAN_EVENT_COMPARE(evt_d, "D", NR_SPAN_DATASTORE, evt_a, 6000, 1000); - SPAN_EVENT_COMPARE_DATASTORE(evt_d, "localhost", "db", "SELECT pass", - "localhost:unknown"); + SPAN_EVENT_COMPARE(evt_d, "D", NR_SPAN_MESSAGE, evt_a, 6000, 1000); + SPAN_EVENT_COMPARE_MESSAGE(evt_d, "queue_name", "aws_sqs", NULL); /* Clean up */ nr_segment_children_deinit(&root.children); @@ -1146,8 +1252,8 @@ static void test_json_print_segments_async_basic(void) { * * These diagrams all follow the same pattern: time is shown in seconds on * the first row, followed by the ROOT node, and then individual contexts - * with their nodes. The "main" context indicates that no async_context will - * be attached to nodes in that context. + * with their nodes. The "main" context indicates that no async_context + * will be attached to nodes in that context. * * time (s) 0 1 2 3 4 5 6 7 8 9 10 * |------------------- ROOT -------------------| @@ -1245,8 +1351,8 @@ static void test_json_print_segments_async_multi_child(void) { /* * Multiple children test: main context lasts the same timespan as ROOT, and - * spawns one child context with three nodes for part of its run time, one of - * which has a duplicated name. + * spawns one child context with three nodes for part of its run time, one + * of which has a duplicated name. * * time (s) 0 1 2 3 4 5 6 7 8 9 10 * |------------------- ROOT -------------------| @@ -1955,7 +2061,8 @@ static void test_json_print_segments_with_sampling_cousin_parent(void) { rv = nr_segment_traces_json_print_segments(buf, span_events, set, set, &txn, &root, segment_names); tlib_pass_if_bool_equal( - "Printing JSON for a sampled cousin parent tree of segments must succeed", + "Printing JSON for a sampled cousin parent tree of segments must " + "succeed", true, rv); test_buffer_contents("Cousin Parent", buf, "[0,14,\"`0\",{},[[1,5,\"`1\",{},[[1,3,\"`2\",{},[]]]],[" @@ -2269,7 +2376,8 @@ static void test_json_print_segments_with_sampling_genghis_khan(void) { rv = nr_segment_traces_json_print_segments(buf, span_events, set, set, &txn, &root, segment_names); tlib_pass_if_bool_equal( - "Printing JSON for a genghis khan sampled tree of segments must succeed", + "Printing JSON for a genghis khan sampled tree of segments must " + "succeed", true, rv); test_buffer_contents("genghis khan", buf, "[0,9,\"`0\",{},[[1,6,\"`1\",{},[]],[3,4,\"`2\",{},[]],[" @@ -2470,7 +2578,8 @@ static void test_trace_create_data_bad_parameters(void) { agent_attributes, user_attributes, intrinsics, true, false); tlib_pass_if_null( - "A transaction with more than NR_MAX_SEGMENTS segments must not succeed " + "A transaction with more than NR_MAX_SEGMENTS segments must not " + "succeed " "in creating " "a trace", metadata.out->trace_json); @@ -2786,9 +2895,10 @@ void test_main(void* p NRUNUSED) { test_json_print_segments_hanoi(); test_json_print_segments_three_siblings(); test_json_print_segments_two_generations(); - test_json_print_segments_datastore_external(); + test_json_print_segments_datastore_external_message(); test_json_print_segments_datastore_params(); test_json_print_segments_external_async_user_attrs(); + test_json_print_segments_message_attributes(); test_json_print_segments_async_basic(); test_json_print_segments_async_multi_child(); diff --git a/axiom/tests/test_span_event.c b/axiom/tests/test_span_event.c index 85ca788b9..ff44ab1cd 100644 --- a/axiom/tests/test_span_event.c +++ b/axiom/tests/test_span_event.c @@ -256,6 +256,55 @@ static void test_span_event_category(void) { tlib_pass_if_str_equal("Category should be the one we set - http", "http", nr_span_event_get_category(event)); + nr_span_event_set_category(event, NR_SPAN_MESSAGE); + tlib_pass_if_str_equal("Category should be the one we set - message", + "message", nr_span_event_get_category(event)); + + nr_span_event_destroy(&event); +} + +static void test_span_event_spankind(void) { + nr_span_event_t* event = nr_span_event_create(); + + // Test : the default is NULL (spankind must be explicitly set) + tlib_pass_if_str_equal( + "When not explicitly set, The default spankind is NULL", NULL, + nr_span_event_get_spankind(event)); + + // Test : A null event returns NULL + tlib_pass_if_null("nr_span_event_get_spankind(NULL) returns NULL", + nr_span_event_get_spankind(NULL)); + + // Test : passing a NULL event should not crash + nr_span_event_set_spankind(NULL, NR_SPANKIND_PRODUCER); + + // Test : invalid spankind + nr_span_event_set_spankind(event, 255); + tlib_pass_if_str_equal( + "Invalid spankind value doesn't crash and sets spankind to none (NULL)", + NULL, nr_span_event_get_spankind(event)); + + // Test : setting the spankind back and forth + nr_span_event_set_spankind(event, NR_SPANKIND_NO_SPANKIND); + tlib_pass_if_str_equal( + "Spankind should be the one we set - no spankind (NULL)", NULL, + nr_span_event_get_spankind(event)); + + // Test : setting the spankind back and forth + nr_span_event_set_spankind(event, NR_SPANKIND_PRODUCER); + tlib_pass_if_str_equal("Spankind should be the one we set - producer", + "producer", nr_span_event_get_spankind(event)); + + // Test : setting the spankind back and forth + nr_span_event_set_spankind(event, NR_SPANKIND_CLIENT); + tlib_pass_if_str_equal("Spankind should be the one we set - client", "client", + nr_span_event_get_spankind(event)); + + // Test : setting the spankind back and forth + nr_span_event_set_spankind(event, NR_SPANKIND_CONSUMER); + tlib_pass_if_str_equal("Spankind should be the one we set - consumer", + "consumer", nr_span_event_get_spankind(event)); + nr_span_event_destroy(&event); } @@ -434,6 +483,62 @@ static void test_span_events_extern_get_and_set(void) { nr_span_event_destroy(&span); } +static void test_span_event_message_string_get_and_set(void) { + nr_span_event_t* event = nr_span_event_create(); + + // Test : that is does not crash when we give the setter a NULL pointer + nr_span_event_set_message(NULL, NR_SPAN_MESSAGE_DESTINATION_NAME, "wallaby"); + tlib_pass_if_null( + "the destination name should still be NULL", + nr_span_event_get_message(event, NR_SPAN_MESSAGE_DESTINATION_NAME)); + nr_span_event_set_message(event, NR_SPAN_MESSAGE_DESTINATION_NAME, NULL); + tlib_pass_if_null( + "given a NULL value we should get a NULL", + nr_span_event_get_message(event, NR_SPAN_MESSAGE_DESTINATION_NAME)); + + // Test : the getter should not crash when we send it an event with a NULL + // component + tlib_pass_if_null( + "NULL event -> NULL component", + nr_span_event_get_message(NULL, NR_SPAN_MESSAGE_DESTINATION_NAME)); + + // Test : send getter invalid range + tlib_pass_if_null("invalid range sent to nr_span_event_get_message", + nr_span_event_get_message(event, 54321)); + + // Test : setting the destination name back and forth behaves as expected + nr_span_event_set_message(event, NR_SPAN_MESSAGE_DESTINATION_NAME, "chicken"); + tlib_pass_if_str_equal( + "should be the component we set 1", "chicken", + nr_span_event_get_message(event, NR_SPAN_MESSAGE_DESTINATION_NAME)); + nr_span_event_set_message(event, NR_SPAN_MESSAGE_DESTINATION_NAME, "oracle"); + tlib_pass_if_str_equal( + "should be the component we set 2", "oracle", + nr_span_event_get_message(event, NR_SPAN_MESSAGE_DESTINATION_NAME)); + + // Test : setting the messaging system back and forth behaves as expected + nr_span_event_set_message(event, NR_SPAN_MESSAGE_MESSAGING_SYSTEM, "chicken"); + tlib_pass_if_str_equal( + "should be the component we set 1", "chicken", + nr_span_event_get_message(event, NR_SPAN_MESSAGE_MESSAGING_SYSTEM)); + nr_span_event_set_message(event, NR_SPAN_MESSAGE_MESSAGING_SYSTEM, "oracle"); + tlib_pass_if_str_equal( + "should be the component we set 2", "oracle", + nr_span_event_get_message(event, NR_SPAN_MESSAGE_MESSAGING_SYSTEM)); + + // Test : setting the server address back and forth behaves as expected + nr_span_event_set_message(event, NR_SPAN_MESSAGE_SERVER_ADDRESS, "chicken"); + tlib_pass_if_str_equal( + "should be the component we set 1", "chicken", + nr_span_event_get_message(event, NR_SPAN_MESSAGE_SERVER_ADDRESS)); + nr_span_event_set_message(event, NR_SPAN_MESSAGE_SERVER_ADDRESS, "oracle"); + tlib_pass_if_str_equal( + "should be the component we set 2", "oracle", + nr_span_event_get_message(event, NR_SPAN_MESSAGE_SERVER_ADDRESS)); + + nr_span_event_destroy(&event); +} + static void test_span_event_error(void) { nr_span_event_t* event = nr_span_event_create(); @@ -614,10 +719,12 @@ void test_main(void* p NRUNUSED) { test_span_event_name(); test_span_event_transaction_name(); test_span_event_category(); + test_span_event_spankind(); test_span_event_timestamp(); test_span_event_duration(); test_span_event_datastore_string_get_and_set(); test_span_events_extern_get_and_set(); + test_span_event_message_string_get_and_set(); test_span_event_error(); test_span_event_set_attribute_user(); test_span_event_txn_parent_attributes(); diff --git a/axiom/tests/test_txn.c b/axiom/tests/test_txn.c index 279bbbbee..5dc68deae 100644 --- a/axiom/tests/test_txn.c +++ b/axiom/tests/test_txn.c @@ -2006,7 +2006,7 @@ static nrtxn_t* create_full_txn_and_reset(nrapp_t* app) { nr_segment_t* seg = nr_segment_start(txn, NULL, NULL); seg->start_time = 5 * NR_TIME_DIVISOR; seg->stop_time = 6 * NR_TIME_DIVISOR; - seg->type = NR_SEGMENT_DATASTORE; + seg->type = NR_SEGMENT_MESSAGE; seg->typed_attributes = nr_zalloc(sizeof(nr_segment_typed_attributes_t)); nr_segment_end(&seg); } @@ -2962,6 +2962,7 @@ static void test_create_rollup_metrics(void) { txn.datastore_products = nr_string_pool_create(); nrm_force_add(txn.unscoped_metrics, "Datastore/all", 4 * NR_TIME_DIVISOR); nrm_force_add(txn.unscoped_metrics, "External/all", 1 * NR_TIME_DIVISOR); + nrm_force_add(txn.unscoped_metrics, "MessageBroker/all", 1 * NR_TIME_DIVISOR); nrm_force_add(txn.unscoped_metrics, "Datastore/MongoDB/all", 2 * NR_TIME_DIVISOR); nrm_force_add(txn.unscoped_metrics, "Datastore/SQLite/all", @@ -2977,6 +2978,9 @@ static void test_create_rollup_metrics(void) { "{\"name\":\"External\\/" "all\",\"data\":[1,1.00000,1.00000,1.00000,1.00000,1." "00000],\"forced\":true}," + "{\"name\":\"MessageBroker\\/" + "all\",\"data\":[1,1.00000,1.00000,1.00000,1.00000,1." + "00000],\"forced\":true}," "{\"name\":\"Datastore\\/MongoDB\\/" "all\",\"data\":[1,2.00000,2.00000,2.00000,2.00000,4." "00000],\"forced\":true}," @@ -2989,6 +2993,9 @@ static void test_create_rollup_metrics(void) { "{\"name\":\"External\\/" "allOther\",\"data\":[1,1.00000,1.00000,1.00000,1." "00000,1.00000],\"forced\":true}," + "{\"name\":\"MessageBroker\\/" + "allOther\",\"data\":[1,1.00000,1.00000,1.00000,1." + "00000,1.00000],\"forced\":true}," "{\"name\":\"Datastore\\/MongoDB\\/" "allOther\",\"data\":[1,2.00000,2.00000,2.00000,2." "00000,4.00000],\"forced\":true}," @@ -3910,6 +3917,7 @@ static void test_error_to_event(void) { nrm_add(txn.unscoped_metrics, "Datastore/all", 1 * NR_TIME_DIVISOR); nrm_add(txn.unscoped_metrics, "External/all", 2 * NR_TIME_DIVISOR); + nrm_add(txn.unscoped_metrics, "MessageBroker/all", 1 * NR_TIME_DIVISOR); nrm_add(txn.unscoped_metrics, "WebFrontend/QueueTime", 3 * NR_TIME_DIVISOR); event = nr_error_to_event(&txn); @@ -3926,8 +3934,10 @@ static void test_error_to_event(void) { "\"queueDuration\":3.00000," "\"externalDuration\":2.00000," "\"databaseDuration\":1.00000," + "\"messageDuration\":1.00000," "\"databaseCallCount\":1," "\"externalCallCount\":1," + "\"messageCallCount\":1," "\"nr.transactionGuid\":\"abcd\"," "\"guid\":\"abcd\"" "}," @@ -3951,8 +3961,10 @@ static void test_error_to_event(void) { "\"queueDuration\":3.00000," "\"externalDuration\":2.00000," "\"databaseDuration\":1.00000," + "\"messageDuration\":1.00000," "\"databaseCallCount\":1," "\"externalCallCount\":1," + "\"messageCallCount\":1," "\"nr.transactionGuid\":\"abcd\"," "\"guid\":\"abcd\"," "\"nr.referringTransactionGuid\":\"foo_guid\"," @@ -4041,6 +4053,7 @@ static void test_create_event(void) { nrm_add(txn.unscoped_metrics, "Datastore/all", 1 * NR_TIME_DIVISOR); nrm_add(txn.unscoped_metrics, "External/all", 2 * NR_TIME_DIVISOR); + nrm_add(txn.unscoped_metrics, "MessageBroker/all", 2 * NR_TIME_DIVISOR); nrm_add(txn.unscoped_metrics, "WebFrontend/QueueTime", 3 * NR_TIME_DIVISOR); event = nr_txn_to_event(&txn); @@ -4059,6 +4072,8 @@ static void test_create_event(void) { "\"externalCallCount\":1," "\"databaseDuration\":1.00000," "\"databaseCallCount\":1," + "\"messageDuration\":2.00000," + "\"messageCallCount\":1," "\"error\":false" "}," "{\"user_long\":1}," @@ -4082,6 +4097,8 @@ static void test_create_event(void) { "\"externalCallCount\":1," "\"databaseDuration\":1.00000," "\"databaseCallCount\":1," + "\"messageDuration\":2.00000," + "\"messageCallCount\":1," "\"error\":false" "}," "{\"user_long\":1}," @@ -4108,6 +4125,8 @@ static void test_create_event(void) { "\"externalCallCount\":1," "\"databaseDuration\":1.00000," "\"databaseCallCount\":1," + "\"messageDuration\":2.00000," + "\"messageCallCount\":1," "\"error\":false" "}," "{\"user_long\":1}," @@ -4132,6 +4151,8 @@ static void test_create_event(void) { "\"externalCallCount\":1," "\"databaseDuration\":1.00000," "\"databaseCallCount\":1," + "\"messageDuration\":2.00000," + "\"messageCallCount\":1," "\"error\":false" "}," "{\"user_long\":1}," @@ -4155,6 +4176,8 @@ static void test_create_event(void) { "\"externalCallCount\":1," "\"databaseDuration\":1.00000," "\"databaseCallCount\":1," + "\"messageDuration\":2.00000," + "\"messageCallCount\":1," "\"error\":false" "}," "{\"user_long\":1}," @@ -8059,7 +8082,8 @@ static void test_segment_record_error(void) { /* Do not add to current segment */ nr_txn_record_error(txn, 0.5, false /* do not add to current segment*/, - "low priority message", "low priority class", "[\"A\",\"B\"]"); + "low priority message", "low priority class", + "[\"A\",\"B\"]"); tlib_pass_if_not_null("Txn error event created", txn->error); tlib_pass_if_null("Segment error NOT created", segment->error); tlib_pass_if_str_equal("Correct txn error.message", "low priority message", @@ -8068,7 +8092,8 @@ static void test_segment_record_error(void) { nr_error_get_klass(txn->error)); /* Normal operation: txn error prioritized over previous */ - nr_txn_record_error(txn, 1, true, "error message", "error class", "[\"A\",\"B\"]"); + nr_txn_record_error(txn, 1, true, "error message", "error class", + "[\"A\",\"B\"]"); tlib_pass_if_not_null("Txn error event created", txn->error); tlib_pass_if_not_null("Segment error created", segment->error); diff --git a/tests/integration/synthetics/test_happy_path_with_dt.php b/tests/integration/synthetics/test_happy_path_with_dt.php index bd415e684..14c3b3b88 100644 --- a/tests/integration/synthetics/test_happy_path_with_dt.php +++ b/tests/integration/synthetics/test_happy_path_with_dt.php @@ -62,7 +62,7 @@ "nr.syntheticsJobId": "jjjjjjj-jjjj-1234-jjjj-jjjjjjjjjjjj", "nr.syntheticsMonitorId": "mmmmmmm-mmmm-1234-mmmm-mmmmmmmmmmmm", "externalDuration": "??", - "externalCallCount": 1, + "externalCallCount": 1, "guid": "??", "sampled": true, "priority": "??",