Skip to content

Commit 71bd116

Browse files
mobuchowskisethsamuelclaude
authored
add DO queries to postgres check (DataDog#23123)
* Add data_observability config to Postgres * Add async job * Ddev update * Fmt * initial move to postgres Signed-off-by: mobuchowski <maciej.obuchowski@datadoghq.com> * fix ai issues Signed-off-by: mobuchowski <maciej.obuchowski@datadoghq.com> * move to postgres check Signed-off-by: mobuchowski <maciej.obuchowski@datadoghq.com> * code review updates Signed-off-by: mobuchowski <maciej.obuchowski@datadoghq.com> * Fix test_config and test_config_defaults for DATA_OBSERVABILITY feature Add DATA_OBSERVABILITY to the expected feature set in test_initialize_features_enabled_and_disabled and add the data_observability defaults entry to EXPECTED_DEFAULTS in test_config_defaults. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: mobuchowski <maciej.obuchowski@datadoghq.com> * Strip trailing semicolons before subquery wrapping Queries with a trailing semicolon would produce invalid SQL when wrapped in SELECT * FROM (...) _dd_row_limit LIMIT N. Strip trailing semicolons and whitespace from the query string before constructing the wrapper. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: mobuchowski <maciej.obuchowski@datadoghq.com> * Switch query_status gauge to query_executions counter with status tag Replace gauge(query_status, 1/0) with count(query_executions, 1, status:success/error). A counter allows computing failure rate over time; a gauge hides flakiness because a single successful check in a window obscures preceding failures. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: mobuchowski <maciej.obuchowski@datadoghq.com> * Remove subquery wrapping; rely on fetchmany for row cap The SELECT * FROM (...) _dd_row_limit LIMIT N wrapper broke on real RC queries which have trailing semicolons or trailing -- Datadog {...} comments appended by the Go handler. Both cause syntax errors inside the subquery. fetchmany(MAX_RESULT_ROWS) is a sufficient memory safety net on the Python side. Executing the query as-is avoids all SQL manipulation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: mobuchowski <maciej.obuchowski@datadoghq.com> * address code review Signed-off-by: mobuchowski <maciej.obuchowski@datadoghq.com> * fmt Signed-off-by: mobuchowski <maciej.obuchowski@datadoghq.com> * explanation Signed-off-by: mobuchowski <maciej.obuchowski@datadoghq.com> * process grouped queries by db, use per-db conn Signed-off-by: mobuchowski <maciej.obuchowski@datadoghq.com> * run with newer ddev Signed-off-by: mobuchowski <maciej.obuchowski@datadoghq.com> --------- Signed-off-by: mobuchowski <maciej.obuchowski@datadoghq.com> Co-authored-by: Seth Samuel <seth.samuel@datadoghq.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 7792076 commit 71bd116

12 files changed

Lines changed: 890 additions & 1 deletion

File tree

postgres/assets/configuration/spec.yaml

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ files:
2828
- name: port
2929
description: The port to use when connecting to PostgreSQL.
3030
fleet_configurable: true
31-
formats: ["port"]
3231
value:
3332
type: integer
3433
display_default: 5432
@@ -543,6 +542,103 @@ files:
543542
type: boolean
544543
example: false
545544
display_default: false
545+
- name: data_observability
546+
fleet_configurable: true
547+
description: |
548+
Configure the Data Observability async job, which executes monitoring
549+
queries delivered via Remote Configuration.
550+
options:
551+
- name: enabled
552+
fleet_configurable: true
553+
description: Enable the Data Observability job.
554+
value:
555+
type: boolean
556+
example: false
557+
display_default: false
558+
- name: collection_interval
559+
fleet_configurable: true
560+
description: |
561+
How often (in seconds) the job wakes up to check which queries are
562+
due. Individual queries have their own interval_seconds. This value
563+
must be less than or equal to the smallest interval_seconds across
564+
all configured queries (i.e. the least common denominator), otherwise
565+
queries with short intervals will be delayed.
566+
value:
567+
type: number
568+
example: 10
569+
display_default: 10
570+
- name: run_sync
571+
hidden: true
572+
description: Run the job synchronously (for testing).
573+
value:
574+
type: boolean
575+
example: false
576+
display_default: false
577+
- name: config_id
578+
hidden: true
579+
description: |
580+
Unique identifier of the Remote Configuration payload that populated
581+
this data_observability block. Set automatically by the Datadog Agent
582+
Go RC handler when it delivers query configuration; do not set this
583+
field manually. It is forwarded as-is on every emitted event so that
584+
the intake can correlate results back to the originating RC config.
585+
value:
586+
type: string
587+
- name: queries
588+
hidden: true
589+
description: |
590+
List of monitor queries to execute. Populated automatically by the
591+
Datadog Agent Go RC handler when Remote Configuration delivers a
592+
DO_QUERY_ACTIONS payload; do not set this field manually. Each entry
593+
describes one SQL monitoring query along with its scheduling
594+
parameters and entity metadata.
595+
value:
596+
type: array
597+
items:
598+
type: object
599+
required:
600+
- monitor_id
601+
- dbname
602+
- query
603+
- interval_seconds
604+
- entity
605+
properties:
606+
- name: monitor_id
607+
type: integer
608+
- name: dbname
609+
type: string
610+
- name: type
611+
type: string
612+
- name: query
613+
type: string
614+
- name: interval_seconds
615+
type: integer
616+
- name: entity
617+
type: object
618+
required:
619+
- platform
620+
- account
621+
- database
622+
- schema
623+
- table
624+
properties:
625+
- name: platform
626+
type: string
627+
- name: account
628+
type: string
629+
- name: database
630+
type: string
631+
- name: schema
632+
type: string
633+
- name: table
634+
type: string
635+
- name: custom_sql_select_fields
636+
type: object
637+
properties:
638+
- name: metric_config_id
639+
type: integer
640+
- name: entity_id
641+
type: string
546642
- name: pg_stat_statements_view
547643
description: |
548644
Set this value if you want to define a custom view or function to allow the datadog user to query the

postgres/changelog.d/23123.added

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add Data Observability async job for executing monitoring queries delivered via Remote Configuration.

postgres/datadog_checks/postgres/config.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ def build_config(check: PostgreSql) -> Tuple[InstanceConfig, ValidationResult]:
121121
"dbm": instance.get(
122122
'dbm', instance.get('deep_database_monitoring', defaults.instance_dbm())
123123
), # Deprecated, use `dbm` instead
124+
"data_observability": {
125+
**dict_defaults.instance_data_observability().model_dump(),
126+
**(instance.get('data_observability') or {}),
127+
},
124128
"custom_metrics": map_custom_metrics(
125129
instance.get('custom_metrics', [])
126130
), # Deprecated, use `custom_queries` instead
@@ -431,6 +435,7 @@ def apply_features(config: InstanceConfig, validation_result: ValidationResult):
431435
validation_result.add_feature(FeatureKey.QUERY_METRICS, config.query_metrics.enabled and config.dbm)
432436
validation_result.add_feature(FeatureKey.COLLECT_SETTINGS, config.collect_settings.enabled and config.dbm)
433437
validation_result.add_feature(FeatureKey.COLLECT_SCHEMAS, config.collect_schemas.enabled and config.dbm)
438+
validation_result.add_feature(FeatureKey.DATA_OBSERVABILITY, config.data_observability.enabled)
434439

435440

436441
METRIC_TYPES = {

postgres/datadog_checks/postgres/config_models/dict_defaults.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,13 @@ def instance_locks_idle_in_transaction():
132132
collection_interval=300,
133133
max_rows=100,
134134
)
135+
136+
137+
def instance_data_observability():
138+
return instance.DataObservability(
139+
enabled=False,
140+
collection_interval=10,
141+
run_sync=False,
142+
config_id=None,
143+
queries=(),
144+
)

postgres/datadog_checks/postgres/config_models/instance.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,53 @@ class CustomQuery(BaseModel):
111111
tags: Optional[tuple[str, ...]] = None
112112

113113

114+
class CustomSqlSelectFields(BaseModel):
115+
model_config = ConfigDict(
116+
arbitrary_types_allowed=True,
117+
frozen=True,
118+
)
119+
entity_id: Optional[str] = None
120+
metric_config_id: Optional[int] = None
121+
122+
123+
class Entity(BaseModel):
124+
model_config = ConfigDict(
125+
arbitrary_types_allowed=True,
126+
frozen=True,
127+
)
128+
account: str
129+
database: str
130+
platform: str
131+
schema_: str = Field(..., alias='schema')
132+
table: str
133+
134+
135+
class Query(BaseModel):
136+
model_config = ConfigDict(
137+
arbitrary_types_allowed=True,
138+
frozen=True,
139+
)
140+
custom_sql_select_fields: Optional[CustomSqlSelectFields] = None
141+
dbname: str
142+
entity: Entity
143+
interval_seconds: int
144+
monitor_id: int
145+
query: str
146+
type: Optional[str] = None
147+
148+
149+
class DataObservability(BaseModel):
150+
model_config = ConfigDict(
151+
arbitrary_types_allowed=True,
152+
frozen=True,
153+
)
154+
collection_interval: Optional[float] = None
155+
config_id: Optional[str] = None
156+
enabled: Optional[bool] = None
157+
queries: Optional[tuple[Query, ...]] = None
158+
run_sync: Optional[bool] = None
159+
160+
114161
class DatabaseAutodiscovery(BaseModel):
115162
model_config = ConfigDict(
116163
arbitrary_types_allowed=True,
@@ -263,6 +310,7 @@ class InstanceConfig(BaseModel):
263310
custom_metrics: Optional[tuple[MappingProxyType[str, Any], ...]] = None
264311
custom_queries: Optional[tuple[CustomQuery, ...]] = None
265312
data_directory: Optional[str] = None
313+
data_observability: Optional[DataObservability] = None
266314
database_autodiscovery: Optional[DatabaseAutodiscovery] = None
267315
database_identifier: Optional[DatabaseIdentifier] = None
268316
database_instance_collection_interval: Optional[float] = None

postgres/datadog_checks/postgres/data/conf.yaml.example

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,25 @@ instances:
434434
#
435435
# dbm: false
436436

437+
## Configure the Data Observability async job, which executes monitoring
438+
## queries delivered via Remote Configuration.
439+
#
440+
# data_observability:
441+
442+
## @param enabled - boolean - optional - default: false
443+
## Enable the Data Observability job.
444+
#
445+
# enabled: false
446+
447+
## @param collection_interval - number - optional - default: 10
448+
## How often (in seconds) the job wakes up to check which queries are
449+
## due. Individual queries have their own interval_seconds. This value
450+
## must be less than or equal to the smallest interval_seconds across
451+
## all configured queries (i.e. the least common denominator), otherwise
452+
## queries with short intervals will be delayed.
453+
#
454+
# collection_interval: 10
455+
437456
## @param pg_stat_statements_view - string - optional - default: pg_stat_statements
438457
## Set this value if you want to define a custom view or function to allow the datadog user to query the
439458
## `pg_stat_statements` table, which is useful for restricting the permissions given to the datadog agent.

0 commit comments

Comments
 (0)