Skip to content

Commit d0c1f61

Browse files
na-jiclaudeeric-weaver
authored
[SDBM-2648] Add collect_execution_plans option to SQL Server DBM (DataDog#23961)
* [SDBM-2648] Add collect_execution_plans option to SQL Server DBM Adds a new `collect_execution_plans.enabled` config flag (default: true) that lets users disable execution plan collection independently of query metrics. When false, the query-metrics job still runs and emits metrics and FQT events, but skips the _collect_plans loop. SDBM-2648 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Add changelog entry for PR DataDog#23961 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Move collect_plans option under query_metrics Plans are collected by the query-metrics job and are already disabled when query_metrics.enabled is false, so the toggle belongs there. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Update changelog entry to reflect query_metrics.collect_plans Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * Run linting --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Eric Weaver <eweaver755@gmail.com> Co-authored-by: Eric Weaver <eric.weaver@datadoghq.com>
1 parent 3ec6c16 commit d0c1f61

6 files changed

Lines changed: 107 additions & 3 deletions

File tree

sqlserver/assets/configuration/spec.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,15 @@ files:
669669
display_default: 2 * collection_interval
670670
example: 600
671671
fleet_configurable: false
672+
- name: collect_plans
673+
description: |
674+
Set to `false` to disable collection of query execution plans while keeping query
675+
metrics collection running. Note that when `enabled` is set to `false`, plan
676+
collection is also disabled regardless of this setting.
677+
value:
678+
type: boolean
679+
example: true
680+
fleet_configurable: false
672681
- name: procedure_metrics
673682
description: Configure collection of procedure metrics
674683
options:

sqlserver/changelog.d/23961.added

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add ``query_metrics.collect_plans`` option to disable SQL Server execution plan collection independently of query metrics.

sqlserver/datadog_checks/sqlserver/config_models/instance.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,7 @@ class QueryMetrics(BaseModel):
389389
arbitrary_types_allowed=True,
390390
frozen=True,
391391
)
392+
collect_plans: Optional[bool] = None
392393
collection_interval: Optional[float] = None
393394
disable_secondary_tags: Optional[bool] = None
394395
dm_exec_query_stats_row_limit: Optional[int] = None

sqlserver/datadog_checks/sqlserver/data/conf.yaml.example

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,13 @@ instances:
420420
#
421421
# lookback_window: 600
422422

423+
## @param collect_plans - boolean - optional - default: true
424+
## Set to `false` to disable collection of query execution plans while keeping query
425+
## metrics collection running. Note that when `enabled` is set to `false`, plan
426+
## collection is also disabled regardless of this setting.
427+
#
428+
# collect_plans: true
429+
423430
## Configure collection of procedure metrics
424431
#
425432
# procedure_metrics:

sqlserver/datadog_checks/sqlserver/statements.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@ def __init__(self, check, config: SQLServerConfig):
291291
self._last_stats_query_time = None
292292
self._max_query_metrics = self._config.statement_metrics_config.get("max_queries", 250)
293293

294+
self._collect_execution_plans = is_affirmative(self._config.statement_metrics_config.get('collect_plans', True))
294295
self._collect_raw_query_statement = self._config.collect_raw_query_statement.get("enabled", False)
295296

296297
def _init_caches(self):
@@ -541,9 +542,12 @@ def collect_statement_metrics_and_plans(self):
541542
self._check.database_monitoring_query_sample(json.dumps(event, default=default_json_event_encoding))
542543
payload = self._to_metrics_payload(rows, self._max_query_metrics)
543544
self._check.database_monitoring_query_metrics(json.dumps(payload, default=default_json_event_encoding))
544-
for event in self._collect_plans(rows, cursor, deadline):
545-
self._check.database_monitoring_query_sample(json.dumps(event, default=default_json_event_encoding))
546-
plans_submitted += 1
545+
if self._collect_execution_plans:
546+
for event in self._collect_plans(rows, cursor, deadline):
547+
self._check.database_monitoring_query_sample(
548+
json.dumps(event, default=default_json_event_encoding)
549+
)
550+
plans_submitted += 1
547551

548552
self._check.count(
549553
"dd.sqlserver.statements.plans_submitted.count", plans_submitted, **self._check.debug_stats_kwargs()

sqlserver/tests/test_statements.py

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1296,3 +1296,85 @@ def _execute_queries():
12961296
database_names = {row['database_name'] for row in sqlserver_rows}
12971297
assert 'datadog_test-1' in database_names, "should have collected metrics for datadog_test-1 databases"
12981298
assert 'master' in database_names, "should have collected metrics for master databases"
1299+
1300+
1301+
@pytest.mark.unit
1302+
@pytest.mark.parametrize(
1303+
"collect_plans_value,expect_plans",
1304+
[
1305+
pytest.param(None, True, id="default_collects_plans"),
1306+
pytest.param(True, True, id="explicitly_enabled"),
1307+
pytest.param(False, False, id="disabled_skips_plans"),
1308+
],
1309+
)
1310+
def test_collect_execution_plans_toggle(instance_docker, collect_plans_value, expect_plans):
1311+
"""query_metrics.collect_plans=false skips plan loop while keeping metrics."""
1312+
instance_docker['dbm'] = True
1313+
query_metrics = {
1314+
'enabled': True,
1315+
'run_sync': True,
1316+
'collection_interval': 0.1,
1317+
'enforce_collection_interval_deadline': False,
1318+
}
1319+
if collect_plans_value is not None:
1320+
query_metrics['collect_plans'] = collect_plans_value
1321+
instance_docker['query_metrics'] = query_metrics
1322+
1323+
check = SQLServer(CHECK_NAME, {}, [instance_docker])
1324+
1325+
fake_row = {
1326+
'query_signature': 'abc123',
1327+
'query_hash': '0xDEAD',
1328+
'query_plan_hash': '0xBEEF',
1329+
'plan_handle': '0000',
1330+
'text': 'SELECT 1',
1331+
'dd_tables': [],
1332+
'dd_commands': [],
1333+
'dd_comments': None,
1334+
'database_name': 'master',
1335+
'is_proc': False,
1336+
'is_encrypted': False,
1337+
'procedure_signature': None,
1338+
'procedure_name': None,
1339+
}
1340+
1341+
fake_plan_event = {
1342+
'dbm_type': 'plan',
1343+
'db': {'query_signature': 'abc123', 'plan': {'definition': '<ShowPlanXML/>'}},
1344+
}
1345+
1346+
def _mock_collect_plans(*_args, **_kwargs):
1347+
yield fake_plan_event
1348+
1349+
with (
1350+
mock.patch.object(
1351+
check.statement_metrics,
1352+
'_collect_metrics_rows',
1353+
return_value=[fake_row],
1354+
),
1355+
mock.patch.object(
1356+
check.statement_metrics,
1357+
'_collect_plans',
1358+
side_effect=_mock_collect_plans,
1359+
) as mock_collect_plans,
1360+
mock.patch.object(
1361+
check.statement_metrics,
1362+
'_rows_to_fqt_events',
1363+
return_value=[],
1364+
),
1365+
mock.patch.object(
1366+
check.statement_metrics,
1367+
'_to_metrics_payload',
1368+
return_value={'sqlserver_rows': [fake_row]},
1369+
),
1370+
mock.patch.object(check, 'database_monitoring_query_metrics'),
1371+
mock.patch.object(check, 'database_monitoring_query_sample'),
1372+
mock.patch.object(check.connection, 'open_managed_default_connection'),
1373+
mock.patch.object(check.connection, 'get_managed_cursor'),
1374+
):
1375+
check.statement_metrics.collect_statement_metrics_and_plans()
1376+
1377+
if expect_plans:
1378+
mock_collect_plans.assert_called_once()
1379+
else:
1380+
mock_collect_plans.assert_not_called()

0 commit comments

Comments
 (0)