Skip to content

Commit d94e1ec

Browse files
authored
[DBMON-5405] Update Mysql integration to use TagManager (#20417)
* Add TagManager class to dbm utils * Add changelog * Switch mysql check to use TagManager * Bump datadog-checks-base * Add changelog
1 parent baeffa3 commit d94e1ec

File tree

7 files changed

+70
-87
lines changed

7 files changed

+70
-87
lines changed

mysql/changelog.d/20417.added

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Update Mysql integration to use TagManager and fix missing dbms_flavor tags

mysql/datadog_checks/mysql/mysql.py

Lines changed: 46 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from datadog_checks.base import AgentCheck, is_affirmative
1919
from datadog_checks.base.utils.db import QueryExecutor, QueryManager
2020
from datadog_checks.base.utils.db.utils import (
21+
TagManager,
2122
default_json_event_encoding,
2223
tracked_query,
2324
)
@@ -119,7 +120,8 @@ def __init__(self, name, init_config, instances):
119120
self._performance_schema_enabled = None
120121
self._events_wait_current_enabled = None
121122
self._config = MySQLConfig(self.instance, init_config)
122-
self.tags = self._config.tags
123+
self.tag_manager = TagManager()
124+
self.tag_manager.set_tags_from_list(self._config.tags, replace=True) # Initialize from static config tags
123125
self.add_core_tags()
124126
self.cloud_metadata = self._config.cloud_metadata
125127

@@ -144,9 +146,6 @@ def __init__(self, name, init_config, instances):
144146
) # type: TTLCache
145147

146148
self._runtime_queries_cached = None
147-
# Keep a copy of the tags without the internal resource tags so they can be used for paths that don't
148-
# go through the agent internal metrics submission processing those tags
149-
self._non_internal_tags = copy.deepcopy(self.tags)
150149
self.set_resource_tags()
151150
self._is_innodb_engine_enabled_cached = None
152151

@@ -185,7 +184,7 @@ def database_identifier(self):
185184
if self._database_identifier is None:
186185
template = Template(self._config.database_identifier.get('template') or '$resolved_hostname')
187186
tag_dict = {}
188-
tags = self.tags.copy()
187+
tags = self.tag_manager.get_tags()
189188
# sort tags to ensure consistent ordering
190189
tags.sort()
191190
for t in tags:
@@ -234,26 +233,31 @@ def add_core_tags(self):
234233
"""
235234
Add tags that should be attached to every metric/event but which require check calculations outside the config.
236235
"""
237-
self.tags.append("database_hostname:{}".format(self.database_hostname))
238-
self.tags.append("database_instance:{}".format(self.database_identifier))
236+
self.tag_manager.set_tag("database_hostname", self.database_hostname, replace=True)
237+
self.tag_manager.set_tag("database_instance", self.database_identifier, replace=True)
239238

240239
def set_resource_tags(self):
241240
if self.cloud_metadata.get("gcp") is not None:
242-
self.tags.append(
243-
"dd.internal.resource:gcp_sql_database_instance:{}:{}".format(
241+
self.tag_manager.set_tag(
242+
"dd.internal.resource",
243+
"gcp_sql_database_instance:{}:{}".format(
244244
self.cloud_metadata.get("gcp")["project_id"], self.cloud_metadata.get("gcp")["instance_id"]
245-
)
245+
),
246246
)
247247
if self.cloud_metadata.get("aws") is not None:
248-
self.tags.append(
249-
"dd.internal.resource:aws_rds_instance:{}".format(
248+
self.tag_manager.set_tag(
249+
"dd.internal.resource",
250+
"aws_rds_instance:{}".format(
250251
self.cloud_metadata.get("aws")["instance_endpoint"],
251-
)
252+
),
252253
)
253254
elif AWS_RDS_HOSTNAME_SUFFIX in self.resolved_hostname:
254255
# allow for detecting if the host is an RDS host, and emit
255256
# the resource properly even if the `aws` config is unset
256-
self.tags.append("dd.internal.resource:aws_rds_instance:{}".format(self.resolved_hostname))
257+
self.tag_manager.set_tag(
258+
"dd.internal.resource",
259+
"aws_rds_instance:{}".format(self.resolved_hostname),
260+
)
257261
self.cloud_metadata["aws"] = {
258262
"instance_endpoint": self.resolved_hostname,
259263
}
@@ -262,32 +266,21 @@ def set_resource_tags(self):
262266
# some `deployment_type`s map to multiple `resource_type`s
263267
resource_type = AZURE_DEPLOYMENT_TYPE_TO_RESOURCE_TYPE.get(deployment_type)
264268
if resource_type:
265-
self.tags.append(
266-
"dd.internal.resource:{}:{}".format(resource_type, self.cloud_metadata.get("azure")["name"])
269+
self.tag_manager.set_tag(
270+
"dd.internal.resource",
271+
"{}:{}".format(resource_type, self.cloud_metadata.get("azure")["name"]),
267272
)
268273
# finally, emit a `database_instance` resource for this instance
269-
self.tags.append(
270-
"dd.internal.resource:database_instance:{}".format(
274+
self.tag_manager.set_tag(
275+
"dd.internal.resource",
276+
"database_instance:{}".format(
271277
self.database_identifier,
272-
)
278+
),
273279
)
274280

275281
def set_version(self, db):
276-
version = get_version(db)
277-
if version == self.version:
278-
return
279-
280-
if self.version and self.version.flavor != version.flavor:
281-
try:
282-
self.tags.remove('dbms_flavor:{}'.format(self.version.flavor.lower()))
283-
except ValueError:
284-
pass
285-
286-
self.version = version
287-
if not self.version.flavor:
288-
return
289-
290-
self.tags.append('dbms_flavor:{}'.format(self.version.flavor.lower()))
282+
self.version = get_version(db)
283+
self.tag_manager.set_tag("dbms_flavor", self.version.flavor.lower(), replace=True)
291284

292285
def _check_database_configuration(self, db):
293286
self._check_performance_schema_enabled(db)
@@ -344,7 +337,7 @@ def _get_debug_tags(self):
344337
return ['agent_hostname:{}'.format(datadog_agent.get_hostname())]
345338

346339
def debug_stats_kwargs(self, tags=None):
347-
tags = self.tags + self._get_debug_tags() + (tags or [])
340+
tags = self.tag_manager.get_tags() + self._get_debug_tags() + (tags or [])
348341
return {
349342
'tags': tags,
350343
"hostname": self.resolved_hostname,
@@ -384,7 +377,7 @@ def check(self, _):
384377
self.check_userstat_enabled(db)
385378

386379
# Metric collection
387-
tags = copy.deepcopy(self.tags)
380+
tags = self.tag_manager.get_tags()
388381
if not self._config.only_custom_queries:
389382
self._collect_metrics(db, tags=tags)
390383
self._collect_system_metrics(self._config.host, db, tags)
@@ -411,13 +404,6 @@ def check(self, _):
411404
self._conn = None
412405
self._report_warnings()
413406

414-
# _set_database_instance_tags sets the tag list for the `database_instance` resource
415-
# based on metadata that is collected on check start. This ensures that we see tags such as
416-
# `replication_role` appear on the database_instance as a host tag.
417-
def _set_database_instance_tags(self, aurora_tags):
418-
tags = copy.deepcopy(self._non_internal_tags)
419-
return list(set(tags) | set(aurora_tags))
420-
421407
def cancel(self):
422408
self._statement_samples.cancel()
423409
self._statement_metrics.cancel()
@@ -524,7 +510,7 @@ def _service_check_tags(self, server=None):
524510
server = self._config.mysql_sock if self._config.mysql_sock != '' else self._config.host
525511
service_check_tags = [
526512
'port:{}'.format(self._config.port if self._config.port else 'unix_socket'),
527-
] + self.tags
513+
] + self.tag_manager.get_tags()
528514
if not self.disable_generic_tags:
529515
service_check_tags.append('server:{0}'.format(server))
530516
return service_check_tags
@@ -686,7 +672,7 @@ def _collect_metrics(self, db, tags):
686672
collected_metric,
687673
)
688674
else:
689-
additional_status_dict[status_dict["name"]] = (status_dict["metric_name"], status_dict["type"])
675+
additional_status_dict[status_name] = (status_metric, status_dict["type"])
690676
metrics.update(additional_status_dict)
691677

692678
if len(self._config.additional_variable) > 0:
@@ -771,7 +757,9 @@ def _collect_group_replica_metrics(self, db, results):
771757
]
772758
if above_802 and len(replica_results) > 2:
773759
additional_tags.append('member_role:{}'.format(replica_results[2]))
774-
self.gauge('mysql.replication.group.member_status', 1, tags=additional_tags + self.tags)
760+
self.gauge(
761+
'mysql.replication.group.member_status', 1, tags=additional_tags + self.tag_manager.get_tags()
762+
)
775763

776764
self.service_check(
777765
self.GROUP_REPLICATION_SERVICE_CHECK_NAME,
@@ -803,7 +791,9 @@ def _collect_group_replica_metrics(self, db, results):
803791
vars_to_submit.update(GROUP_REPLICATION_VARS_8_0_2)
804792

805793
# Submit metrics now, so it's possible to attach `channel_name` tag
806-
self._submit_metrics(vars_to_submit, results, self.tags + ['channel_name:{}'.format(r[0])])
794+
self._submit_metrics(
795+
vars_to_submit, results, self.tag_manager.get_tags() + ['channel_name:{}'.format(r[0])]
796+
)
807797

808798
return vars_to_submit
809799
except Exception as e:
@@ -866,7 +856,7 @@ def _submit_replication_status(self, status, additional_tags):
866856
self.gauge(
867857
name=self.SLAVE_SERVICE_CHECK_NAME,
868858
value=1 if status == AgentCheck.OK else 0,
869-
tags=self.tags + additional_tags,
859+
tags=self.tag_manager.get_tags() + additional_tags,
870860
hostname=self.reported_hostname,
871861
)
872862
# deprecated in favor of service_check("mysql.replication.replica_running")
@@ -981,13 +971,13 @@ def _collect_dict(self, metric_type, field_metric_map, query, db, tags):
981971
self.log.exception("Error while running %s", query)
982972

983973
def _get_runtime_aurora_tags(self, db):
984-
runtime_tags = []
974+
runtime_tags = {}
985975
try:
986976
with closing(db.cursor(CommenterCursor)) as cursor:
987977
cursor.execute(SQL_REPLICATION_ROLE_AWS_AURORA)
988978
replication_role = cursor.fetchone()[0]
989979
if replication_role in {'writer', 'reader'}:
990-
runtime_tags.append('replication_role:' + replication_role)
980+
runtime_tags['replication_role'] = replication_role
991981
except Exception:
992982
self.log.warning("Error occurred while fetching Aurora runtime tags: %s", traceback.format_exc())
993983
return runtime_tags
@@ -998,15 +988,11 @@ def _update_runtime_aurora_tags(self, aurora_tags):
998988
First removes any existing Aurora runtime tags by key name, then adds the new tags.
999989
"""
1000990
# Extract tag keys from aurora_tags to identify which tags to remove
1001-
aurora_tag_keys = {tag.split(':')[0] for tag in aurora_tags}
1002-
1003-
# Remove existing Aurora runtime tags from both tag lists
1004-
self.tags = [tag for tag in self.tags if tag.split(':')[0] not in aurora_tag_keys]
1005-
self._non_internal_tags = [tag for tag in self._non_internal_tags if tag.split(':')[0] not in aurora_tag_keys]
1006-
1007-
# Add the new Aurora tags using set operations
1008-
self.tags = list(set(self.tags) | set(aurora_tags))
1009-
self._non_internal_tags = list(set(self._non_internal_tags) | set(aurora_tags))
991+
for tag, value in aurora_tags.items():
992+
self.tag_manager.set_tag(tag, value, replace=True)
993+
self.tag_manager.set_tag(
994+
"dd.internal.resource", "database_instance:{}".format(self.database_identifier), replace=True
995+
)
1010996

1011997
def _collect_system_metrics(self, host, db, tags):
1012998
pid = None
@@ -1417,7 +1403,7 @@ def _send_database_instance_metadata(self):
14171403
"collection_interval": self._config.database_instance_collection_interval,
14181404
'dbms_version': self.version.version + '+' + self.version.build,
14191405
'integration_version': __version__,
1420-
"tags": self._non_internal_tags,
1406+
"tags": self.tag_manager.get_tags(),
14211407
"timestamp": time.time() * 1000,
14221408
"cloud_metadata": self._config.cloud_metadata,
14231409
"metadata": {

mysql/datadog_checks/mysql/statements.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ def run_job(self):
123123
self._check.gauge(
124124
"dd.mysql.statement_metrics.collect_metrics.elapsed_ms",
125125
(time.time() - start) * 1000,
126-
tags=self._check.tags + self._check._get_debug_tags(),
126+
tags=self._check.tag_manager.get_tags() + self._check._get_debug_tags(),
127127
hostname=self._check.resolved_hostname,
128128
)
129129

mysql/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ classifiers = [
2727
"Private :: Do Not Upload",
2828
]
2929
dependencies = [
30-
"datadog-checks-base>=37.4.0",
30+
"datadog-checks-base>=37.13.0",
3131
]
3232
dynamic = [
3333
"version",

mysql/tests/test_mysql.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ def test_mysql_version_set(aggregator, dd_run_check, instance_basic):
120120
dd_run_check(mysql_check, cancel=False)
121121
dd_run_check(mysql_check, cancel=True)
122122
assert mysql_check.version is not None
123-
assert mysql_check.tags.count('dbms_flavor:{}'.format(mysql_check.version.flavor.lower())) == 1
123+
assert mysql_check.tag_manager.get_tags().count('dbms_flavor:{}'.format(mysql_check.version.flavor.lower())) == 1
124124

125125

126126
@pytest.mark.e2e
@@ -803,6 +803,8 @@ def test_database_instance_metadata(aggregator, dd_run_check, instance_complex,
803803
expected_tags = tags.METRIC_TAGS + [
804804
"database_hostname:{}".format(expected_database_hostname),
805805
"database_instance:{}".format(expected_database_instance),
806+
'dd.internal.resource:database_instance:{}'.format(expected_database_instance),
807+
"dbms_flavor:{}".format(MYSQL_FLAVOR.lower()),
806808
]
807809

808810
mysql_check = MySql(common.CHECK_NAME, {}, [instance_complex])
@@ -881,7 +883,7 @@ def test_propagate_agent_tags(
881883
check = MySql(common.CHECK_NAME, init_config, [instance_basic])
882884
assert check._config._should_propagate_agent_tags(instance_basic, init_config) == should_propagate_agent_tags
883885
if should_propagate_agent_tags:
884-
assert all(tag in check.tags for tag in agent_tags)
886+
assert all(tag in check.tag_manager.get_tags() for tag in agent_tags)
885887
dd_run_check(check)
886888
aggregator.assert_service_check(
887889
'mysql.can_connect',

mysql/tests/test_statements.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,10 +132,10 @@ def run_query(q):
132132
) as m_get_runtime_aurora_tags:
133133
m_obfuscate_sql.side_effect = _obfuscate_sql
134134
m_get_is_aurora.return_value = False
135-
m_get_runtime_aurora_tags.return_value = []
135+
m_get_runtime_aurora_tags.return_value = {}
136136
if aurora_replication_role:
137137
m_get_is_aurora.return_value = True
138-
m_get_runtime_aurora_tags.return_value = ["replication_role:" + aurora_replication_role]
138+
m_get_runtime_aurora_tags.return_value = {"replication_role": aurora_replication_role}
139139

140140
# Run a query
141141
run_query(query)
@@ -433,10 +433,10 @@ def test_statement_samples_collect(
433433
mysql_check, '_get_runtime_aurora_tags', passthrough=True
434434
) as m_get_runtime_aurora_tags:
435435
m_get_is_aurora.return_value = False
436-
m_get_runtime_aurora_tags.return_value = []
436+
m_get_runtime_aurora_tags.return_value = {}
437437
if aurora_replication_role:
438438
m_get_is_aurora.return_value = True
439-
m_get_runtime_aurora_tags.return_value = ["replication_role:" + aurora_replication_role]
439+
m_get_runtime_aurora_tags.return_value = {"replication_role": aurora_replication_role}
440440

441441
logger.debug("running first check")
442442
dd_run_check(mysql_check)

mysql/tests/test_unit.py

Lines changed: 13 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,13 @@ def cursor(self):
5353
writer_row = ('writer',)
5454

5555
tags = mysql_check._get_runtime_aurora_tags(MockDatabase(MockCursor(rows=[reader_row])))
56-
assert tags == ['replication_role:reader']
56+
assert tags == {'replication_role': 'reader'}
5757

5858
tags = mysql_check._get_runtime_aurora_tags(MockDatabase(MockCursor(rows=[writer_row])))
59-
assert tags == ['replication_role:writer']
59+
assert tags == {'replication_role': 'writer'}
6060

6161
tags = mysql_check._get_runtime_aurora_tags(MockDatabase(MockCursor(rows=[(1, 'reader')])))
62-
assert tags == []
62+
assert tags == {}
6363

6464
# Error cases for non-aurora databases; any error should be caught and not fail the check
6565

@@ -70,7 +70,7 @@ def cursor(self):
7070
)
7171
)
7272
)
73-
assert tags == []
73+
assert tags == {}
7474

7575
tags = mysql_check._get_runtime_aurora_tags(
7676
MockDatabase(
@@ -80,7 +80,7 @@ def cursor(self):
8080
)
8181
)
8282
)
83-
assert tags == []
83+
assert tags == {}
8484

8585

8686
def test__get_server_pid():
@@ -456,26 +456,20 @@ def test_update_runtime_aurora_tags():
456456
mysql_check = MySql(common.CHECK_NAME, {}, instances=[{'server': 'localhost', 'user': 'datadog'}])
457457

458458
# Initial state - no tags
459-
assert 'replication_role:writer' not in mysql_check.tags
460-
assert 'replication_role:writer' not in mysql_check._non_internal_tags
459+
assert 'replication_role:writer' not in mysql_check.tag_manager.get_tags()
461460

462461
# First check - writer role
463-
aurora_tags = ['replication_role:writer']
462+
aurora_tags = {'replication_role': 'writer'}
464463
mysql_check._update_runtime_aurora_tags(aurora_tags)
465-
assert 'replication_role:writer' in mysql_check.tags
466-
assert 'replication_role:writer' in mysql_check._non_internal_tags
467-
assert len([t for t in mysql_check.tags if t.startswith('replication_role:')]) == 1
468-
assert len([t for t in mysql_check._non_internal_tags if t.startswith('replication_role:')]) == 1
464+
assert 'replication_role:writer' in mysql_check.tag_manager.get_tags()
465+
assert len([t for t in mysql_check.tag_manager.get_tags() if t.startswith('replication_role:')]) == 1
469466

470467
# Simulate failover - reader role
471-
aurora_tags = ['replication_role:reader']
468+
aurora_tags = {'replication_role': 'reader'}
472469
mysql_check._update_runtime_aurora_tags(aurora_tags)
473-
assert 'replication_role:reader' in mysql_check.tags
474-
assert 'replication_role:reader' in mysql_check._non_internal_tags
475-
assert 'replication_role:writer' not in mysql_check.tags
476-
assert 'replication_role:writer' not in mysql_check._non_internal_tags
477-
assert len([t for t in mysql_check.tags if t.startswith('replication_role:')]) == 1
478-
assert len([t for t in mysql_check._non_internal_tags if t.startswith('replication_role:')]) == 1
470+
assert 'replication_role:reader' in mysql_check.tag_manager.get_tags()
471+
assert 'replication_role:writer' not in mysql_check.tag_manager.get_tags()
472+
assert len([t for t in mysql_check.tag_manager.get_tags() if t.startswith('replication_role:')]) == 1
479473

480474

481475
@pytest.mark.parametrize(

0 commit comments

Comments
 (0)