Skip to content

Commit 13fc336

Browse files
authored
Add initialization health event to MySQL (DataDog#21867)
* Add initialization health event to MySQL * Changelog * Bump base version * Base check * Tags * Lint * Sanitize pass
1 parent 9ce67c5 commit 13fc336

5 files changed

Lines changed: 105 additions & 5 deletions

File tree

mysql/changelog.d/21867.added

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add DBM agent health events to MySQL. Events include basic initialization check, unhandled error, and missed collection.

mysql/datadog_checks/mysql/config.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# (C) Datadog, Inc. 2020-present
22
# All rights reserved
33
# Licensed under Simplified BSD License (see LICENSE)
4+
import copy
5+
46
from datadog_checks.base import ConfigurationError, is_affirmative
57
from datadog_checks.base.log import get_check_logger
68
from datadog_checks.base.utils.aws import rds_parse_tags_from_endpoint
@@ -164,3 +166,11 @@ def _should_propagate_agent_tags(instance, init_config) -> bool:
164166
return init_config_propagate_agent_tags
165167
# if neither the instance nor the init_config has set the value, return False
166168
return False
169+
170+
171+
def sanitize(config: dict) -> dict:
172+
sanitized = copy.deepcopy(config)
173+
sanitized['pass'] = '***' if sanitized.get('pass') else None
174+
sanitized['password'] = '***' if sanitized.get('password') else None
175+
176+
return sanitized
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# (C) Datadog, Inc. 2025-present
2+
# All rights reserved
3+
# Licensed under a 3-clause BSD style license (see LICENSE)
4+
from __future__ import annotations
5+
6+
from typing import TYPE_CHECKING
7+
8+
if TYPE_CHECKING:
9+
from datadog_checks.mysql import MySql
10+
11+
from enum import Enum
12+
13+
from datadog_checks.base.utils.db.health import Health, HealthEvent, HealthStatus
14+
15+
16+
class MySqlHealthEvent(Enum):
17+
"""
18+
Enum representing the health events for MySql monitoring.
19+
"""
20+
21+
EXPLAIN_PLAN_ERROR = 'explain_plan_error'
22+
23+
24+
class MySqlHealth(Health):
25+
def __init__(self, check: MySql):
26+
# type: (MySql) -> None
27+
"""
28+
Initialize the MySqlHealth instance.
29+
30+
:param check: MySql
31+
The check instance that will be used to submit health events.
32+
"""
33+
super().__init__(check)
34+
self.check = check
35+
36+
def submit_health_event(
37+
self,
38+
name: HealthEvent | MySqlHealthEvent,
39+
status: HealthStatus,
40+
tags: list[str] = None,
41+
data: dict = None,
42+
**kwargs,
43+
):
44+
"""
45+
Submit a health event to the aggregator.
46+
47+
:param name: MySqlHealthEvent
48+
The name of the health event.
49+
:param status: HealthStatus
50+
The health status to submit.
51+
:param data: A dictionary to be submitted as `data`. Must be JSON serializable.
52+
"""
53+
super().submit_health_event(
54+
name=name,
55+
status=status,
56+
# If we have an error parsing the config we may not have tags yet
57+
tags=(self.check.tags if hasattr(self.check, 'tags') else []) + (tags or []),
58+
data={
59+
"database_instance": self.check.database_identifier,
60+
"ddagenthostname": self.check.agent_hostname,
61+
**(data or {}),
62+
},
63+
**kwargs,
64+
)

mysql/datadog_checks/mysql/mysql.py

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@
1515
import pymysql
1616
from cachetools import TTLCache
1717

18-
from datadog_checks.base import AgentCheck, is_affirmative
18+
from datadog_checks.base import AgentCheck, DatabaseCheck, is_affirmative
1919
from datadog_checks.base.utils.db import QueryExecutor, QueryManager
20+
from datadog_checks.base.utils.db.health import HealthEvent, HealthStatus
2021
from datadog_checks.base.utils.db.utils import (
2122
TagManager,
2223
default_json_event_encoding,
@@ -28,11 +29,12 @@
2829
from datadog_checks.base.utils.serialization import json
2930
from datadog_checks.mysql import aws
3031
from datadog_checks.mysql.cursor import CommenterCursor, CommenterDictCursor, CommenterSSCursor
32+
from datadog_checks.mysql.health import MySqlHealth
3133

3234
from .__about__ import __version__
3335
from .activity import MySQLActivity
3436
from .collection_utils import collect_all_scalars, collect_scalar, collect_string, collect_type
35-
from .config import MySQLConfig
37+
from .config import MySQLConfig, sanitize
3638
from .const import (
3739
AWS_RDS_HOSTNAME_SUFFIX,
3840
AZURE_DEPLOYMENT_TYPE_TO_RESOURCE_TYPE,
@@ -95,12 +97,12 @@
9597
PSUTIL_AVAILABLE = False
9698

9799
try:
98-
import datadog_agent
100+
import datadog_agent # type: ignore
99101
except ImportError:
100102
from datadog_checks.base.stubs import datadog_agent
101103

102104

103-
class MySql(AgentCheck):
105+
class MySql(DatabaseCheck):
104106
SERVICE_CHECK_NAME = 'mysql.can_connect'
105107
SLAVE_SERVICE_CHECK_NAME = 'mysql.replication.slave_running'
106108
REPLICA_SERVICE_CHECK_NAME = 'mysql.replication.replica_running'
@@ -110,6 +112,7 @@ class MySql(AgentCheck):
110112

111113
def __init__(self, name, init_config, instances):
112114
super(MySql, self).__init__(name, init_config, instances)
115+
self.health = MySqlHealth(self)
113116
self.qcache_stats = {}
114117
self.version = None
115118
self.is_mariadb = None
@@ -122,6 +125,7 @@ def __init__(self, name, init_config, instances):
122125
self._events_wait_current_enabled = None
123126
self._group_replication_active = None
124127
self._replication_role = None
128+
self._initialized_at = int(time.time() * 1000)
125129
self._config = MySQLConfig(self.instance, init_config)
126130
self.tag_manager = TagManager()
127131
self.tag_manager.set_tags_from_list(self._config.tags, replace=True) # Initialize from static config tags
@@ -167,6 +171,21 @@ def __init__(self, name, init_config, instances):
167171
self.set_resource_tags()
168172
self._is_innodb_engine_enabled_cached = None
169173

174+
self._submit_initialization_health_event()
175+
176+
def _submit_initialization_health_event(self):
177+
try:
178+
# Handle the config validation result after we've set tags so those tags are included in the health event
179+
# TODO: validate the config once it is refactored similar to Postgres, and then send the computed config
180+
self.health.submit_health_event(
181+
name=HealthEvent.INITIALIZATION,
182+
status=HealthStatus.OK,
183+
cooldown_time=60 * 60 * 6, # 6 hours
184+
data={"initialized_at": self._initialized_at, "instance": sanitize(self.instance)},
185+
)
186+
except Exception as e:
187+
self.log.error("Error submitting health event for initialization: %s", e)
188+
170189
def execute_query_raw(self, query):
171190
with closing(self._conn.cursor(CommenterSSCursor)) as cursor:
172191
cursor.execute(query)
@@ -179,6 +198,10 @@ def _send_metadata(self):
179198
self.set_metadata('flavor', self.version.flavor)
180199
self.set_metadata('resolved_hostname', self.resolved_hostname)
181200

201+
@property
202+
def tags(self):
203+
return self.tag_manager.get_tags()
204+
182205
@property
183206
def reported_hostname(self):
184207
# type: () -> str
@@ -352,6 +375,8 @@ def get_library_versions(cls):
352375
return {'pymysql': pymysql.__version__}
353376

354377
def check(self, _):
378+
self._submit_initialization_health_event()
379+
355380
if self.instance.get('user'):
356381
self._log_deprecation('_config_renamed', 'user', 'username')
357382

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.21.0",
30+
"datadog-checks-base>=37.22.1",
3131
]
3232
dynamic = [
3333
"version",

0 commit comments

Comments
 (0)