diff --git a/ibm_mq/assets/configuration/spec.yaml b/ibm_mq/assets/configuration/spec.yaml index addefdfafeb92..d67c241aa6592 100644 --- a/ibm_mq/assets/configuration/spec.yaml +++ b/ibm_mq/assets/configuration/spec.yaml @@ -205,6 +205,29 @@ files: value: example: false type: boolean + - name: add_description_tags + description: | + Add description tags to channel and queue metrics. When enabled, the following tags will be added: + - channel_desc: for channel metrics + - queue_desc: for queue metrics + + Note: Enabling this option may increase tag cardinality depending on how many unique + descriptions you have configured for your channels and queues. + value: + example: false + type: boolean + - name: normalize_description_tags + description: | + Normalize description tag values when add_description_tags is enabled. + When enabled, descriptions are automatically normalized for use as tag values: + - Converted to lowercase + - Spaces and special characters replaced with underscores + - Limited to 200 characters + + See: https://docs.datadoghq.com/getting_started/tagging/#define-tags + value: + example: true + type: boolean - name: mqcd_version description: | Which channel definition version to use. Supported values are 1 to 9 including. diff --git a/ibm_mq/changelog.d/21948.added b/ibm_mq/changelog.d/21948.added new file mode 100644 index 0000000000000..2f1725cdea9df --- /dev/null +++ b/ibm_mq/changelog.d/21948.added @@ -0,0 +1 @@ +Add collecting channel and queue desc fields as tags for metrics diff --git a/ibm_mq/datadog_checks/ibm_mq/collectors/channel_metric_collector.py b/ibm_mq/datadog_checks/ibm_mq/collectors/channel_metric_collector.py index 39c1be86dae20..850ddcf26b1c7 100644 --- a/ibm_mq/datadog_checks/ibm_mq/collectors/channel_metric_collector.py +++ b/ibm_mq/datadog_checks/ibm_mq/collectors/channel_metric_collector.py @@ -7,6 +7,7 @@ from datadog_checks.base.log import CheckLoggingAdapter # noqa: F401 from datadog_checks.ibm_mq import metrics from datadog_checks.ibm_mq.config import IBMMQConfig # noqa: F401 +from datadog_checks.ibm_mq.utils import normalize_desc_tag try: import pymqi @@ -60,6 +61,16 @@ def get_pcf_channel_metrics(self, queue_manager): for channel_info in discovered_channels: channel_name = to_string(channel_info[pymqi.CMQCFC.MQCACH_CHANNEL_NAME]).strip() channel_tags = self.config.tags_no_channel + ["channel:{}".format(channel_name)] + + # Add channel description as tag if enabled + if self.config.add_description_tags and pymqi.CMQCFC.MQCACH_DESC in channel_info: + channel_desc = to_string(channel_info[pymqi.CMQCFC.MQCACH_DESC]).strip() + if channel_desc: + if self.config.normalize_description_tags: + channel_desc = normalize_desc_tag(channel_desc) + if channel_desc: + channel_tags.append("channel_desc:{}".format(channel_desc)) + self._submit_metrics_from_properties( channel_info, channel_name, metrics.channel_metrics(), channel_tags ) @@ -154,6 +165,15 @@ def _submit_channel_status(self, queue_manager, search_channel_name, tags, chann continue channel_tags = tags + ["channel:{}".format(channel_name)] + # Add channel description as tag if enabled + if self.config.add_description_tags and pymqi.CMQCFC.MQCACH_DESC in channel_info: + channel_desc = to_string(channel_info[pymqi.CMQCFC.MQCACH_DESC]).strip() + if channel_desc: + if self.config.normalize_description_tags: + channel_desc = normalize_desc_tag(channel_desc) + if channel_desc: + channel_tags.append("channel_desc:{}".format(channel_desc)) + self._submit_metrics_from_properties( channel_info, channel_name, metrics.channel_status_metrics(), channel_tags ) diff --git a/ibm_mq/datadog_checks/ibm_mq/collectors/queue_metric_collector.py b/ibm_mq/datadog_checks/ibm_mq/collectors/queue_metric_collector.py index 89159c822517d..376c8f6605986 100644 --- a/ibm_mq/datadog_checks/ibm_mq/collectors/queue_metric_collector.py +++ b/ibm_mq/datadog_checks/ibm_mq/collectors/queue_metric_collector.py @@ -9,6 +9,7 @@ from datadog_checks.ibm_mq import metrics from datadog_checks.ibm_mq.config import IBMMQConfig # noqa: F401 from datadog_checks.ibm_mq.metrics import GAUGE +from datadog_checks.ibm_mq.utils import normalize_desc_tag try: import pymqi @@ -265,6 +266,16 @@ def queue_stats(self, queue_manager, queue_name, tags): for queue_info in response: usage = KNOWN_USAGES.get(queue_info.get(pymqi.CMQC.MQIA_USAGE), 'unknown') enriched_tags.append('queue_usage:{}'.format(usage)) + + # Add queue description as tag if enabled + if self.config.add_description_tags and pymqi.CMQC.MQCA_Q_DESC in queue_info: + queue_desc = to_string(queue_info[pymqi.CMQC.MQCA_Q_DESC]).strip() + if queue_desc: + if self.config.normalize_description_tags: + queue_desc = normalize_desc_tag(queue_desc) + if queue_desc: + enriched_tags.append('queue_desc:{}'.format(queue_desc)) + self._submit_queue_stats(queue_info, queue_name, enriched_tags) finally: if pcf is not None: diff --git a/ibm_mq/datadog_checks/ibm_mq/config.py b/ibm_mq/datadog_checks/ibm_mq/config.py index 4a66995053702..11dad0da407a5 100644 --- a/ibm_mq/datadog_checks/ibm_mq/config.py +++ b/ibm_mq/datadog_checks/ibm_mq/config.py @@ -84,6 +84,8 @@ def __init__(self, instance, init_config): self.collect_statistics_metrics = is_affirmative(instance.get('collect_statistics_metrics', False)) # type: bool self.collect_reset_queue_metrics = is_affirmative(instance.get('collect_reset_queue_metrics', True)) self.collect_connection_metrics = is_affirmative(instance.get('collect_connection_metrics', True)) + self.add_description_tags = is_affirmative(instance.get('add_description_tags', False)) # type: bool + self.normalize_description_tags = is_affirmative(instance.get('normalize_description_tags', True)) # type: bool if int(self.auto_discover_queues) + int(bool(self.queue_patterns)) + int(bool(self.queue_regex)) > 1: self.log.warning( "Configurations auto_discover_queues, queue_patterns and queue_regex are not intended to be used " diff --git a/ibm_mq/datadog_checks/ibm_mq/config_models/defaults.py b/ibm_mq/datadog_checks/ibm_mq/config_models/defaults.py index 0e736bbc233e2..dcb99ddc6f142 100644 --- a/ibm_mq/datadog_checks/ibm_mq/config_models/defaults.py +++ b/ibm_mq/datadog_checks/ibm_mq/config_models/defaults.py @@ -12,6 +12,10 @@ def shared_queue_manager_process_limit(): return 1 +def instance_add_description_tags(): + return False + + def instance_auto_discover_channels(): return True @@ -60,6 +64,10 @@ def instance_mqcd_version(): return 6 +def instance_normalize_description_tags(): + return True + + def instance_override_hostname(): return False diff --git a/ibm_mq/datadog_checks/ibm_mq/config_models/instance.py b/ibm_mq/datadog_checks/ibm_mq/config_models/instance.py index 2bae634d1582b..d05af0f94ee6a 100644 --- a/ibm_mq/datadog_checks/ibm_mq/config_models/instance.py +++ b/ibm_mq/datadog_checks/ibm_mq/config_models/instance.py @@ -35,6 +35,7 @@ class InstanceConfig(BaseModel): arbitrary_types_allowed=True, frozen=True, ) + add_description_tags: Optional[bool] = None auto_discover_channels: Optional[bool] = None auto_discover_queues: Optional[bool] = None auto_discover_queues_via_names: Optional[bool] = None @@ -52,6 +53,7 @@ class InstanceConfig(BaseModel): metric_patterns: Optional[MetricPatterns] = None min_collection_interval: Optional[float] = None mqcd_version: Optional[float] = Field(None, ge=1.0) + normalize_description_tags: Optional[bool] = None override_hostname: Optional[bool] = None password: Optional[str] = Field(None, min_length=1) port: Optional[int] = None diff --git a/ibm_mq/datadog_checks/ibm_mq/data/conf.yaml.example b/ibm_mq/datadog_checks/ibm_mq/data/conf.yaml.example index fed5e9359c5b2..419f471b1a277 100644 --- a/ibm_mq/datadog_checks/ibm_mq/data/conf.yaml.example +++ b/ibm_mq/datadog_checks/ibm_mq/data/conf.yaml.example @@ -183,6 +183,27 @@ instances: # # collect_connection_metrics: false + ## @param add_description_tags - boolean - optional - default: false + ## Add description tags to channel and queue metrics. When enabled, the following tags will be added: + ## - channel_desc: for channel metrics + ## - queue_desc: for queue metrics + ## + ## Note: Enabling this option may increase tag cardinality depending on how many unique + ## descriptions you have configured for your channels and queues. + # + # add_description_tags: false + + ## @param normalize_description_tags - boolean - optional - default: true + ## Normalize description tag values when add_description_tags is enabled. + ## When enabled, descriptions are automatically normalized for use as tag values: + ## - Converted to lowercase + ## - Spaces and special characters replaced with underscores + ## - Limited to 200 characters + ## + ## See: https://docs.datadoghq.com/getting_started/tagging/#define-tags + # + # normalize_description_tags: true + ## @param mqcd_version - number - optional - default: 6 ## Which channel definition version to use. Supported values are 1 to 9 including. ## If you're having connection issues make sure it matches your MQ version. diff --git a/ibm_mq/datadog_checks/ibm_mq/utils.py b/ibm_mq/datadog_checks/ibm_mq/utils.py index a815903a9a28b..e556dfd335ba5 100644 --- a/ibm_mq/datadog_checks/ibm_mq/utils.py +++ b/ibm_mq/datadog_checks/ibm_mq/utils.py @@ -1,6 +1,7 @@ # (C) Datadog, Inc. 2018-present # All rights reserved # Licensed under a 3-clause BSD style license (see LICENSE) +import re from datetime import datetime from dateutil import tz @@ -21,6 +22,34 @@ def sanitize_strings(s): return s.strip() +def normalize_desc_tag(desc): + """ + Normalize description strings for use as tag values. + https://docs.datadoghq.com/getting_started/tagging/#define-tags + """ + if not desc: + return '' + + # Convert to lowercase + normalized = desc.lower() + + # Replace spaces and special characters with underscores + # Keep only alphanumeric, hyphens, and underscores + normalized = re.sub(r'[^a-z0-9\-_]', '_', normalized) + + # Replace multiple consecutive underscores with single underscore + normalized = re.sub(r'_+', '_', normalized) + + # Strip leading/trailing underscores + normalized = normalized.strip('_') + + # Limit length (Datadog recommends keeping tag values reasonable) + if len(normalized) > 200: + normalized = normalized[:200].rstrip('_') + + return normalized + + def calculate_elapsed_time(datestamp, timestamp, qm_timezone, current_time=None): """ Calculate elapsed time in seconds from IBM MQ queue status date and timestamps