Skip to content

Commit ef4cd5b

Browse files
authored
Add queue and channel desc fields as tags (#21948)
* test feature of adding description to metrics * initial implementation of description for channels and queues * config options and validations * lint * add tag normalization * lint * tests * tests * refactor * lint
1 parent 8a14901 commit ef4cd5b

File tree

12 files changed

+230
-0
lines changed

12 files changed

+230
-0
lines changed

ibm_mq/assets/configuration/spec.yaml

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,29 @@ files:
205205
value:
206206
example: false
207207
type: boolean
208+
- name: add_description_tags
209+
description: |
210+
Add description tags to channel and queue metrics. When enabled, the following tags will be added:
211+
- channel_desc:<description> for channel metrics
212+
- queue_desc:<description> for queue metrics
213+
214+
Note: Enabling this option may increase tag cardinality depending on how many unique
215+
descriptions you have configured for your channels and queues.
216+
value:
217+
example: false
218+
type: boolean
219+
- name: normalize_description_tags
220+
description: |
221+
Normalize description tag values when add_description_tags is enabled.
222+
When enabled, descriptions are automatically normalized for use as tag values:
223+
- Converted to lowercase
224+
- Spaces and special characters replaced with underscores
225+
- Limited to 200 characters
226+
227+
See: https://docs.datadoghq.com/getting_started/tagging/#define-tags
228+
value:
229+
example: true
230+
type: boolean
208231
- name: mqcd_version
209232
description: |
210233
Which channel definition version to use. Supported values are 1 to 9 including.

ibm_mq/changelog.d/21948.added

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add collecting channel and queue desc fields as tags for metrics

ibm_mq/datadog_checks/ibm_mq/collectors/channel_metric_collector.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from datadog_checks.base.log import CheckLoggingAdapter # noqa: F401
88
from datadog_checks.ibm_mq import metrics
99
from datadog_checks.ibm_mq.config import IBMMQConfig # noqa: F401
10+
from datadog_checks.ibm_mq.utils import normalize_desc_tag
1011

1112
try:
1213
import pymqi
@@ -50,6 +51,15 @@ def __init__(
5051
self.service_check = service_check
5152
self.gauge = gauge
5253

54+
def _add_channel_description_tag(self, channel_info, channel_tags):
55+
# Add channel description as a tag, normalizing if configured.
56+
channel_desc = to_string(channel_info[pymqi.CMQCFC.MQCACH_DESC]).strip()
57+
if channel_desc:
58+
if self.config.normalize_description_tags:
59+
channel_desc = normalize_desc_tag(channel_desc)
60+
if channel_desc:
61+
channel_tags.append("channel_desc:{}".format(channel_desc))
62+
5363
def get_pcf_channel_metrics(self, queue_manager):
5464
discovered_channels = self._discover_channels(queue_manager)
5565
if discovered_channels:
@@ -60,6 +70,9 @@ def get_pcf_channel_metrics(self, queue_manager):
6070
for channel_info in discovered_channels:
6171
channel_name = to_string(channel_info[pymqi.CMQCFC.MQCACH_CHANNEL_NAME]).strip()
6272
channel_tags = self.config.tags_no_channel + ["channel:{}".format(channel_name)]
73+
if self.config.add_description_tags and pymqi.CMQCFC.MQCACH_DESC in channel_info:
74+
self._add_channel_description_tag(channel_info, channel_tags)
75+
6376
self._submit_metrics_from_properties(
6477
channel_info, channel_name, metrics.channel_metrics(), channel_tags
6578
)
@@ -153,6 +166,8 @@ def _submit_channel_status(self, queue_manager, search_channel_name, tags, chann
153166
if channel_name in channels_to_skip:
154167
continue
155168
channel_tags = tags + ["channel:{}".format(channel_name)]
169+
if self.config.add_description_tags and pymqi.CMQCFC.MQCACH_DESC in channel_info:
170+
self._add_channel_description_tag(channel_info, channel_tags)
156171

157172
self._submit_metrics_from_properties(
158173
channel_info, channel_name, metrics.channel_status_metrics(), channel_tags

ibm_mq/datadog_checks/ibm_mq/collectors/queue_metric_collector.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from datadog_checks.ibm_mq import metrics
1010
from datadog_checks.ibm_mq.config import IBMMQConfig # noqa: F401
1111
from datadog_checks.ibm_mq.metrics import GAUGE
12+
from datadog_checks.ibm_mq.utils import normalize_desc_tag
1213

1314
try:
1415
import pymqi
@@ -265,6 +266,16 @@ def queue_stats(self, queue_manager, queue_name, tags):
265266
for queue_info in response:
266267
usage = KNOWN_USAGES.get(queue_info.get(pymqi.CMQC.MQIA_USAGE), 'unknown')
267268
enriched_tags.append('queue_usage:{}'.format(usage))
269+
270+
# Add queue description as tag if enabled
271+
if self.config.add_description_tags and pymqi.CMQC.MQCA_Q_DESC in queue_info:
272+
queue_desc = to_string(queue_info[pymqi.CMQC.MQCA_Q_DESC]).strip()
273+
if queue_desc:
274+
if self.config.normalize_description_tags:
275+
queue_desc = normalize_desc_tag(queue_desc)
276+
if queue_desc:
277+
enriched_tags.append('queue_desc:{}'.format(queue_desc))
278+
268279
self._submit_queue_stats(queue_info, queue_name, enriched_tags)
269280
finally:
270281
if pcf is not None:

ibm_mq/datadog_checks/ibm_mq/config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ def __init__(self, instance, init_config):
8484
self.collect_statistics_metrics = is_affirmative(instance.get('collect_statistics_metrics', False)) # type: bool
8585
self.collect_reset_queue_metrics = is_affirmative(instance.get('collect_reset_queue_metrics', True))
8686
self.collect_connection_metrics = is_affirmative(instance.get('collect_connection_metrics', True))
87+
self.add_description_tags = is_affirmative(instance.get('add_description_tags', False)) # type: bool
88+
self.normalize_description_tags = is_affirmative(instance.get('normalize_description_tags', True)) # type: bool
8789
if int(self.auto_discover_queues) + int(bool(self.queue_patterns)) + int(bool(self.queue_regex)) > 1:
8890
self.log.warning(
8991
"Configurations auto_discover_queues, queue_patterns and queue_regex are not intended to be used "

ibm_mq/datadog_checks/ibm_mq/config_models/defaults.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ def shared_queue_manager_process_limit():
1212
return 1
1313

1414

15+
def instance_add_description_tags():
16+
return False
17+
18+
1519
def instance_auto_discover_channels():
1620
return True
1721

@@ -60,6 +64,10 @@ def instance_mqcd_version():
6064
return 6
6165

6266

67+
def instance_normalize_description_tags():
68+
return True
69+
70+
6371
def instance_override_hostname():
6472
return False
6573

ibm_mq/datadog_checks/ibm_mq/config_models/instance.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class InstanceConfig(BaseModel):
3535
arbitrary_types_allowed=True,
3636
frozen=True,
3737
)
38+
add_description_tags: Optional[bool] = None
3839
auto_discover_channels: Optional[bool] = None
3940
auto_discover_queues: Optional[bool] = None
4041
auto_discover_queues_via_names: Optional[bool] = None
@@ -52,6 +53,7 @@ class InstanceConfig(BaseModel):
5253
metric_patterns: Optional[MetricPatterns] = None
5354
min_collection_interval: Optional[float] = None
5455
mqcd_version: Optional[float] = Field(None, ge=1.0)
56+
normalize_description_tags: Optional[bool] = None
5557
override_hostname: Optional[bool] = None
5658
password: Optional[str] = Field(None, min_length=1)
5759
port: Optional[int] = None

ibm_mq/datadog_checks/ibm_mq/data/conf.yaml.example

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,27 @@ instances:
183183
#
184184
# collect_connection_metrics: false
185185

186+
## @param add_description_tags - boolean - optional - default: false
187+
## Add description tags to channel and queue metrics. When enabled, the following tags will be added:
188+
## - channel_desc:<description> for channel metrics
189+
## - queue_desc:<description> for queue metrics
190+
##
191+
## Note: Enabling this option may increase tag cardinality depending on how many unique
192+
## descriptions you have configured for your channels and queues.
193+
#
194+
# add_description_tags: false
195+
196+
## @param normalize_description_tags - boolean - optional - default: true
197+
## Normalize description tag values when add_description_tags is enabled.
198+
## When enabled, descriptions are automatically normalized for use as tag values:
199+
## - Converted to lowercase
200+
## - Spaces and special characters replaced with underscores
201+
## - Limited to 200 characters
202+
##
203+
## See: https://docs.datadoghq.com/getting_started/tagging/#define-tags
204+
#
205+
# normalize_description_tags: true
206+
186207
## @param mqcd_version - number - optional - default: 6
187208
## Which channel definition version to use. Supported values are 1 to 9 including.
188209
## If you're having connection issues make sure it matches your MQ version.

ibm_mq/datadog_checks/ibm_mq/utils.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# (C) Datadog, Inc. 2018-present
22
# All rights reserved
33
# Licensed under a 3-clause BSD style license (see LICENSE)
4+
import re
45
from datetime import datetime
56

67
from dateutil import tz
@@ -21,6 +22,34 @@ def sanitize_strings(s):
2122
return s.strip()
2223

2324

25+
def normalize_desc_tag(desc):
26+
"""
27+
Normalize description strings for use as tag values.
28+
https://docs.datadoghq.com/getting_started/tagging/#define-tags
29+
"""
30+
if not desc:
31+
return ''
32+
33+
# Convert to lowercase
34+
normalized = desc.lower()
35+
36+
# Replace spaces and special characters with underscores
37+
# Keep only alphanumeric, hyphens, and underscores
38+
normalized = re.sub(r'[^a-z0-9\-_]', '_', normalized)
39+
40+
# Replace multiple consecutive underscores with single underscore
41+
normalized = re.sub(r'_+', '_', normalized)
42+
43+
# Strip leading/trailing underscores
44+
normalized = normalized.strip('_')
45+
46+
# Limit length (Datadog recommends keeping tag values reasonable)
47+
if len(normalized) > 200:
48+
normalized = normalized[:200].rstrip('_')
49+
50+
return normalized
51+
52+
2453
def calculate_elapsed_time(datestamp, timestamp, qm_timezone, current_time=None):
2554
"""
2655
Calculate elapsed time in seconds from IBM MQ queue status date and timestamps

ibm_mq/tests/test_channel_metric_collector.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,50 @@ def test_collect_connection_metrics_config_option(instance, collect_connection_m
217217
)
218218

219219

220+
@pytest.mark.parametrize(
221+
'add_description_tags,normalize_description_tags,channel_desc,expected_desc_tag',
222+
[
223+
(False, True, b'Test Description', None), # Disabled
224+
(True, True, b'Test Description', 'channel_desc:test_description'), # Enabled + normalized
225+
(True, False, b'Test Description', 'channel_desc:Test Description'), # Enabled + raw
226+
(True, True, b'', None), # Empty description
227+
],
228+
)
229+
def test_channel_description_tags(
230+
instance, add_description_tags, normalize_description_tags, channel_desc, expected_desc_tag
231+
):
232+
"""Test channel description tags with different config options."""
233+
instance['add_description_tags'] = add_description_tags
234+
instance['normalize_description_tags'] = normalize_description_tags
235+
236+
channel_info = {
237+
pymqi.CMQCFC.MQCACH_CHANNEL_NAME: b'TEST.CHANNEL',
238+
pymqi.CMQCFC.MQIACH_BATCH_SIZE: 100,
239+
}
240+
if channel_desc:
241+
channel_info[pymqi.CMQCFC.MQCACH_DESC] = channel_desc
242+
243+
collector = _get_mocked_instance(instance)
244+
collector._discover_channels = Mock(return_value=[channel_info])
245+
collector.gauge = Mock()
246+
queue_manager = Mock()
247+
248+
collector.get_pcf_channel_metrics(queue_manager)
249+
250+
# Find gauge calls with channel-specific tags (skip the first call which is for total channel count)
251+
gauge_calls = collector.gauge.call_args_list
252+
channel_metric_calls = [call for call in gauge_calls if 'channel:TEST.CHANNEL' in call[1]['tags']]
253+
assert len(channel_metric_calls) > 0, "Expected at least one channel-specific metric call"
254+
255+
channel_tags = channel_metric_calls[0][1]['tags']
256+
257+
if expected_desc_tag:
258+
assert expected_desc_tag in channel_tags
259+
else:
260+
desc_tags = [t for t in channel_tags if t.startswith('channel_desc:')]
261+
assert len(desc_tags) == 0
262+
263+
220264
def _get_mocked_instance(instance):
221265
config = IBMMQConfig(instance, {})
222266
collector = ChannelMetricCollector(config, service_check=Mock(), gauge=Mock(), log=Mock())

0 commit comments

Comments
 (0)