Repository: DataDog/integrations-core
Pull request #20035: add replication slot information and replication slot type to postgres replication stat metric
Author: login: shreesampath
Labels: name: integration/postgres
name: team/agent-integrations
name: team/database-monitoring-agent
name: ecosystems/review-requested
name: product/review-requested
name: agent/review-requested
Created: 2025-04-08T15:19:58Z | Merged: 2025-04-10T12:53:01Z

### What does this PR do?
<!-- A brief description of the change being made with this pull request. -->
(This is a draft PR)
JIRA - https://datadoghq.atlassian.net/browse/ADPPOS-2295 
This PR adds replication slot name and replication slot type to Postgres replication stat metrics.

Testing -
![Screenshot 2025-04-09 at 10 14 15 AM](https://github.com/user-attachments/assets/6d2d778e-8b80-482c-81fe-f52a87394789)

### Motivation
<!-- What inspired you to submit this pull request? -->

Getting Postgres replication slot information is helpful to know what type of replication (physical or logical) is lagging behind the primary instance and set up monitoring and alerting accordingly.

### Review checklist (to be filled by reviewers)

- [ ] Feature or bugfix MUST have appropriate tests (unit, integration, e2e)
- [ ] Add the `qa/skip-qa` label if the PR doesn't need to be tested during QA.
- [ ] If you need to backport this PR to another branch, you can add the `backport/<branch-name>` label to the PR and it will automatically open a backport PR once this one is merged



Diff:
diff --git a/postgres/changelog.d/20035.added b/postgres/changelog.d/20035.added
new file mode 100644
index 0000000000000..980ea2e057def
--- /dev/null
+++ b/postgres/changelog.d/20035.added
@@ -0,0 +1 @@
+add replication slot information and replication slot type to postgres replication stat metric
diff --git a/postgres/datadog_checks/postgres/discovery.py b/postgres/datadog_checks/postgres/discovery.py
index 3ec4b9fab86fa..89434dea3eee5 100644
--- a/postgres/datadog_checks/postgres/discovery.py
+++ b/postgres/datadog_checks/postgres/discovery.py
@@ -26,7 +26,7 @@ def __init__(
         super(PostgresAutodiscovery, self).__init__(
             self._get_databases,
             # parent class asks for includelist to be a dictionary
-            include={db: 0 for db in autodiscovery_config.get("include", [".*"])},
+            include=dict.fromkeys(autodiscovery_config.get("include", [".*"]), 0),
             exclude=autodiscovery_config.get("exclude", DEFAULT_EXCLUDES),
             interval=autodiscovery_config.get("refresh", DEFAULT_REFRESH),
         )
diff --git a/postgres/datadog_checks/postgres/metrics_cache.py b/postgres/datadog_checks/postgres/metrics_cache.py
index e825cf9011e36..2b50e4a604508 100644
--- a/postgres/datadog_checks/postgres/metrics_cache.py
+++ b/postgres/datadog_checks/postgres/metrics_cache.py
@@ -27,7 +27,6 @@
     REPLICATION_METRICS_9_1,
     REPLICATION_METRICS_9_2,
     REPLICATION_METRICS_10,
-    REPLICATION_STATS_METRICS,
 )
 from .version_utils import V8_3, V9, V9_1, V9_2, V9_4, V9_6, V10, V12, V14, V17
 
@@ -182,11 +181,6 @@ def get_replication_metrics(self, version, is_aurora):
                 self.replication_metrics.update(REPLICATION_METRICS_9_2)
         return self.replication_metrics
 
-    def get_replication_stats_metrics(self, version):
-        if version >= V10 and self.replication_stats_metrics is None:
-            self.replication_stats_metrics = dict(REPLICATION_STATS_METRICS)
-        return self.replication_stats_metrics
-
     def get_activity_metrics(self, version):
         """Use ACTIVITY_METRICS_LT_8_3 or ACTIVITY_METRICS_8_3 or ACTIVITY_METRICS_9_2
         depending on the postgres version in conjunction with ACTIVITY_QUERY_10 or ACTIVITY_QUERY_LT_10.
diff --git a/postgres/datadog_checks/postgres/postgres.py b/postgres/datadog_checks/postgres/postgres.py
index 76b6aafc39273..985ec7fdaaafc 100644
--- a/postgres/datadog_checks/postgres/postgres.py
+++ b/postgres/datadog_checks/postgres/postgres.py
@@ -51,6 +51,7 @@
     QUERY_PG_CONTROL_CHECKPOINT_LT_10,
     QUERY_PG_REPLICATION_SLOTS,
     QUERY_PG_REPLICATION_SLOTS_STATS,
+    QUERY_PG_REPLICATION_STATS_METRICS,
     QUERY_PG_STAT_DATABASE,
     QUERY_PG_STAT_DATABASE_CONFLICTS,
     QUERY_PG_STAT_WAL_RECEIVER,
@@ -320,6 +321,7 @@ def dynamic_queries(self):
             if self._config.collect_buffercache_metrics:
                 queries.append(BUFFERCACHE_METRICS)
             queries.append(QUERY_PG_REPLICATION_SLOTS)
+            queries.append(QUERY_PG_REPLICATION_STATS_METRICS)
             queries.append(VACUUM_PROGRESS_METRICS if self.version >= V17 else VACUUM_PROGRESS_METRICS_LT_17)
             queries.append(STAT_SUBSCRIPTION_METRICS)
 
@@ -740,10 +742,6 @@ def _collect_stats(self, instance_tags):
             replication_metrics_query['metrics'] = replication_metrics
             metric_scope.append(replication_metrics_query)
 
-        replication_stats_metrics = self.metrics_cache.get_replication_stats_metrics(self.version)
-        if replication_stats_metrics:
-            metric_scope.append(replication_stats_metrics)
-
         with self.db() as conn:
             with conn.cursor(cursor_factory=CommenterCursor) as cursor:
                 results_len = self._query_scope(cursor, db_instance_metrics, instance_tags, False)
diff --git a/postgres/datadog_checks/postgres/util.py b/postgres/datadog_checks/postgres/util.py
index 315ae16ac5f86..44112caf5bbfc 100644
--- a/postgres/datadog_checks/postgres/util.py
+++ b/postgres/datadog_checks/postgres/util.py
@@ -381,37 +381,34 @@ def get_list_chunks(lst, n):
 }
 
 # Requires postgres 10+
-REPLICATION_STATS_METRICS = {
-    'descriptors': [
-        ('application_name', 'wal_app_name'),
-        ('state', 'wal_state'),
-        ('sync_state', 'wal_sync_state'),
-        ('client_addr', 'wal_client_addr'),
-    ],
-    'metrics': {
-        'GREATEST (0, EXTRACT(epoch from write_lag)) as write_lag': (
-            'replication.wal_write_lag',
-            AgentCheck.gauge,
-        ),
-        'GREATEST (0, EXTRACT(epoch from flush_lag)) AS flush_lag': (
-            'replication.wal_flush_lag',
-            AgentCheck.gauge,
-        ),
-        'GREATEST (0, EXTRACT(epoch from replay_lag)) AS replay_lag': (
-            'replication.wal_replay_lag',
-            AgentCheck.gauge,
-        ),
-        'GREATEST (0, age(backend_xmin)) as backend_xmin_age': (
-            'replication.backend_xmin_age',
-            AgentCheck.gauge,
-        ),
-    },
-    'relation': False,
-    'query': """
-SELECT application_name, state, sync_state, client_addr, {metrics_columns}
-FROM pg_stat_replication
-""",
+QUERY_PG_REPLICATION_STATS_METRICS = {
     'name': 'replication_stats_metrics',
+    'query': """
+SELECT
+    pg_stat_replication.application_name,
+    pg_stat_replication.state,
+    pg_stat_replication.sync_state,
+    pg_stat_replication.client_addr,
+    pg_stat_replication_slot.slot_name,
+    pg_stat_replication_slot.slot_type,
+    GREATEST (0, EXTRACT(epoch from pg_stat_replication.write_lag)) as write_lag,
+    GREATEST (0, EXTRACT(epoch from pg_stat_replication.replay_lag)) AS replay_lag,
+    GREATEST (0, age(pg_stat_replication.backend_xmin)) AS backend_xmin_age
+FROM pg_stat_replication as pg_stat_replication
+LEFT JOIN pg_replication_slots as pg_stat_replication_slot
+ON pg_stat_replication.pid = pg_stat_replication_slot.active_pid;
+""".strip(),
+    'columns': [
+        {'name': 'wal_app_name', 'type': 'tag'},
+        {'name': 'wal_state', 'type': 'tag'},
+        {'name': 'wal_sync_state', 'type': 'tag'},
+        {'name': 'wal_client_addr', 'type': 'tag'},
+        {'name': 'slot_name', 'type': 'tag_not_null'},
+        {'name': 'slot_type', 'type': 'tag_not_null'},
+        {'name': 'wal_write_lag', 'type': 'gauge'},
+        {'name': 'wal_flush_lag', 'type': 'gauge'},
+        {'name': 'wal_replay_lag', 'type': 'gauge'},
+    ],
 }
 
 
diff --git a/postgres/tests/common.py b/postgres/tests/common.py
index c3793375dd723..7eac562e7bdf3 100644
--- a/postgres/tests/common.py
+++ b/postgres/tests/common.py
@@ -15,9 +15,9 @@
     QUERY_PG_CONTROL_CHECKPOINT,
     QUERY_PG_REPLICATION_SLOTS,
     QUERY_PG_REPLICATION_SLOTS_STATS,
+    QUERY_PG_REPLICATION_STATS_METRICS,
     QUERY_PG_STAT_WAL_RECEIVER,
     QUERY_PG_UPTIME,
-    REPLICATION_STATS_METRICS,
     SLRU_METRICS,
     SNAPSHOT_TXID_METRICS,
     STAT_IO_METRICS,
@@ -42,7 +42,9 @@
 POSTGRES_VERSION = os.environ.get('POSTGRES_VERSION', None)
 POSTGRES_IMAGE = "alpine"
 
-REPLICA_CONTAINER_NAME = 'compose-postgres_replica-1'
+REPLICA_CONTAINER_1_NAME = 'compose-postgres_replica-1'
+REPLICA_CONTAINER_2_NAME = 'compose-postgres_replica2-1'
+REPLICA_LOGICAL_1_NAME = 'compose-postgres_logical_replica-1'
 USING_LATEST = False
 
 if POSTGRES_VERSION is not None:
@@ -261,16 +263,34 @@ def check_activity_metrics(aggregator, tags, hostname=None, count=1):
         aggregator.assert_metric(name, count=1, tags=tags, hostname=hostname)
 
 
-def check_stat_replication(aggregator, expected_tags, count=1):
+def check_stat_replication_physical_slot(aggregator, expected_tags, count=1):
     if float(POSTGRES_VERSION) < 10:
         return
     replication_tags = expected_tags + [
         'wal_app_name:walreceiver',
-        'wal_client_addr:{}'.format(get_container_ip(REPLICA_CONTAINER_NAME)),
+        f'wal_client_addr:{get_container_ip(REPLICA_CONTAINER_1_NAME)}',
+        'wal_state:streaming',
+        'wal_sync_state:async',
+        'slot_name:replication_slot',
+        'slot_type:physical',
+    ]
+    for metric_name in _iterate_metric_name(QUERY_PG_REPLICATION_STATS_METRICS):
+        aggregator.assert_metric(metric_name, count=count, tags=replication_tags)
+
+
+def check_stat_replication_no_slot(aggregator, expected_tags, count=1):
+    if float(POSTGRES_VERSION) < 10:
+        return
+    wal_app_name = 'replica2'
+    if float(POSTGRES_VERSION) < 12:
+        wal_app_name = 'walreceiver'
+    replication_tags = expected_tags + [
+        f'wal_app_name:{wal_app_name}',
+        f'wal_client_addr:{get_container_ip(REPLICA_CONTAINER_2_NAME)}',
         'wal_state:streaming',
         'wal_sync_state:async',
     ]
-    for metric_name in _iterate_metric_name(REPLICATION_STATS_METRICS):
+    for metric_name in _iterate_metric_name(QUERY_PG_REPLICATION_STATS_METRICS):
         aggregator.assert_metric(metric_name, count=count, tags=replication_tags)
 
 
diff --git a/postgres/tests/test_pg_integration.py b/postgres/tests/test_pg_integration.py
index 26f8865d26119..9b2f92e510026 100644
--- a/postgres/tests/test_pg_integration.py
+++ b/postgres/tests/test_pg_integration.py
@@ -39,7 +39,8 @@
     check_slru_metrics,
     check_snapshot_txid_metrics,
     check_stat_io_metrics,
-    check_stat_replication,
+    check_stat_replication_no_slot,
+    check_stat_replication_physical_slot,
     check_stat_wal_metrics,
     check_uptime_metrics,
     check_wal_receiver_metrics,
@@ -81,7 +82,8 @@ def test_common_metrics(aggregator, integration_check, pg_instance, is_aurora):
     check_conflict_metrics(aggregator, expected_tags=expected_tags)
     check_db_count(aggregator, expected_tags=expected_tags)
     check_slru_metrics(aggregator, expected_tags=expected_tags)
-    check_stat_replication(aggregator, expected_tags=expected_tags)
+    check_stat_replication_physical_slot(aggregator, expected_tags=expected_tags)
+    check_stat_replication_no_slot(aggregator, expected_tags=expected_tags)
     if is_aurora is False:
         check_wal_receiver_metrics(aggregator, expected_tags=expected_tags, connected=0)
         check_file_wal_metrics(aggregator, expected_tags=expected_tags)
diff --git a/postgres/tests/test_replication_slot.py b/postgres/tests/test_replication_slot.py
index c33fa66cc326c..c80c315680e8e 100644
--- a/postgres/tests/test_replication_slot.py
+++ b/postgres/tests/test_replication_slot.py
@@ -6,12 +6,20 @@
 import psycopg2
 import pytest
 
-from datadog_checks.postgres.util import QUERY_PG_REPLICATION_SLOTS_STATS
-
-from .common import DB_NAME, HOST, _get_expected_tags, _iterate_metric_name, assert_metric_at_least
+from datadog_checks.dev.docker import get_container_ip
+from datadog_checks.postgres.util import QUERY_PG_REPLICATION_SLOTS_STATS, QUERY_PG_REPLICATION_STATS_METRICS
+
+from .common import (
+    DB_NAME,
+    HOST,
+    REPLICA_LOGICAL_1_NAME,
+    _get_expected_tags,
+    _iterate_metric_name,
+    assert_metric_at_least,
+)
 from .utils import requires_over_10, requires_over_14
 
-pytestmark = [pytest.mark.integration, pytest.mark.usefixtures('dd_environment')]
+pytestmark = [pytest.mark.integration, pytest.mark.usefixtures("dd_environment")]
 
 
 @requires_over_10
@@ -26,7 +34,7 @@ def test_physical_replication_slots(aggregator, integration_check, pg_instance):
         with conn.cursor() as cur:
             cur.execute("select pg_wal_lsn_diff(pg_current_wal_lsn(), redo_lsn) from pg_control_checkpoint();")
             redo_lsn_age = int(cur.fetchall()[0][0])
-            cur.execute('select age(xmin) FROM pg_replication_slots;')
+            cur.execute("select age(xmin) FROM pg_replication_slots;")
             bound = cur.fetchall()[0][0]
             xmin_age_higher_bound += int(bound) if bound is not None else 0
 
@@ -45,27 +53,27 @@ def test_physical_replication_slots(aggregator, integration_check, pg_instance):
     # Nothing reported for phys_1
     base_tags = _get_expected_tags(check, pg_instance)
     expected_phys2_tags = base_tags + [
-        'slot_name:phys_2',
-        'slot_persistence:permanent',
-        'slot_state:inactive',
-        'slot_type:physical',
+        "slot_name:phys_2",
+        "slot_persistence:permanent",
+        "slot_state:inactive",
+        "slot_type:physical",
     ]
     expected_phys3_tags = base_tags + [
-        'slot_name:phys_3',
-        'slot_persistence:temporary',
-        'slot_state:active',
-        'slot_type:physical',
+        "slot_name:phys_3",
+        "slot_persistence:temporary",
+        "slot_state:active",
+        "slot_type:physical",
     ]
     expected_repslot_tags = base_tags + [
-        'slot_name:replication_slot',
-        'slot_persistence:permanent',
-        'slot_state:active',
-        'slot_type:physical',
+        "slot_name:replication_slot",
+        "slot_persistence:permanent",
+        "slot_state:active",
+        "slot_type:physical",
     ]
 
     assert_metric_at_least(
         aggregator,
-        'postgresql.replication_slot.restart_delay_bytes',
+        "postgresql.replication_slot.restart_delay_bytes",
         lower_bound=redo_lsn_age,
         tags=expected_phys2_tags,
         count=1,
@@ -73,14 +81,14 @@ def test_physical_replication_slots(aggregator, integration_check, pg_instance):
 
     assert_metric_at_least(
         aggregator,
-        'postgresql.replication_slot.restart_delay_bytes',
+        "postgresql.replication_slot.restart_delay_bytes",
         lower_bound=redo_lsn_age,
         tags=expected_phys3_tags,
         count=1,
     )
     assert_metric_at_least(
         aggregator,
-        'postgresql.replication_slot.xmin_age',
+        "postgresql.replication_slot.xmin_age",
         higher_bound=xmin_age_higher_bound,
         tags=expected_repslot_tags,
         count=1,
@@ -99,22 +107,22 @@ def test_logical_replication_slots(aggregator, integration_check, pg_instance):
 
     base_tags = _get_expected_tags(check, pg_instance)
     expected_tags = base_tags + [
-        'slot_name:logical_slot',
-        'slot_persistence:permanent',
-        'slot_state:inactive',
-        'slot_type:logical',
+        "slot_name:logical_slot",
+        "slot_persistence:permanent",
+        "slot_state:inactive",
+        "slot_type:logical",
     ]
     # Both should be in the past
     assert_metric_at_least(
         aggregator,
-        'postgresql.replication_slot.confirmed_flush_delay_bytes',
+        "postgresql.replication_slot.confirmed_flush_delay_bytes",
         count=1,
         lower_bound=50,
         tags=expected_tags,
     )
     assert_metric_at_least(
         aggregator,
-        'postgresql.replication_slot.restart_delay_bytes',
+        "postgresql.replication_slot.restart_delay_bytes",
         count=1,
         lower_bound=restart_age,
         tags=expected_tags,
@@ -127,9 +135,29 @@ def test_replication_slot_stats(aggregator, integration_check, pg_instance):
     check.check(pg_instance)
 
     expected_tags = _get_expected_tags(check, pg_instance) + [
-        'slot_name:logical_slot',
-        'slot_state:inactive',
-        'slot_type:logical',
+        "slot_name:logical_slot",
+        "slot_state:inactive",
+        "slot_type:logical",
     ]
     for metric_name in _iterate_metric_name(QUERY_PG_REPLICATION_SLOTS_STATS):
         aggregator.assert_metric(metric_name, count=1, tags=expected_tags)
+
+
+@requires_over_10
+def test_replication_slot_information(aggregator, integration_check, pg_instance):
+    client_address = None
+    check = integration_check(pg_instance)
+    client_address = get_container_ip(REPLICA_LOGICAL_1_NAME)
+    check.check(pg_instance)
+    base_tags = _get_expected_tags(check, pg_instance)
+    expected_tags = base_tags + [
+        "wal_app_name:subscription_cities",
+        "wal_state:streaming",
+        "wal_sync_state:async",
+        "wal_client_addr:" + client_address,
+        "slot_name:subscription_cities",
+        "slot_type:logical",
+    ]
+
+    for metric_name in _iterate_metric_name(QUERY_PG_REPLICATION_STATS_METRICS):
+        assert_metric_at_least(aggregator, metric_name, count=1, lower_bound=0, tags=expected_tags)


Reviews:
user: login: bonnefoa
body: You need to add an additional test to check the tags are correctly set. A good place would probably be `test_logical_replication_slots` in `tests/test_replication_slot.py`

On the test docker, I have the following:
```
SELECT replication.application_name,
replication.state,
replication.sync_state,
replication.client_addr,
replication_slot.slot_name,
replication_slot.slot_type, GREATEST (0, EXTRACT(epoch from replication.write_lag)) as write_lag

FROM pg_stat_replication as replication
LEFT JOIN pg_replication_slots as replication_slot
ON replication.pid = replication_slot.active_pid;
  application_name   |   state   | sync_state | client_addr |      slot_name      | slot_type | write_lag
---------------------+-----------+------------+-------------+---------------------+-----------+-----------
 subscription_cities | streaming | async      | 172.23.0.2  | subscription_cities | logical   |         0
 walreceiver         | streaming | async      | 172.23.0.5  | replication_slot    | physical  |         0
 replica2            | streaming | async      | 172.23.0.3  |                     |           |         0
```

So you could add something like this
```
    expected_rep_stats_tags = base_tags + [
        'wal_app_name:subscription_cities',
        'state:streaming',
        'sync_state:async',
        'client_addr:172.23.0.2',
        'slot_name:subscription_cities',
        'slot_type:logical',
    ]
    assert_metric_at_least(
        aggregator,
        'postgresql.replication.wal_write_lag',
        lower_bound=0, # We only check the tags, we don't care about the value
        tags=expected_rep_stats_tags,
        count=1,
    )
```

However, the `client_addr` is likely going to change. So you will need to get it first somehow. 
```
    cursor.execute("SELECT <get the ip here>")
    client_addr = float(cursor.fetchone()[0])
```
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-08T15:56:43Z
user: login: shreesampath
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T14:24:03Z
user: login: shreesampath
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T15:53:29Z
user: login: bonnefoa
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-10T12:10:35Z
user: login: nenadnoveljic
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-10T12:51:21Z

Review comments:
diff_hunk: @@ -383,33 +383,43 @@ def get_list_chunks(lst, n):
 # Requires postgres 10+
 REPLICATION_STATS_METRICS = {
     'descriptors': [
-        ('application_name', 'wal_app_name'),
-        ('state', 'wal_state'),
-        ('sync_state', 'wal_sync_state'),
-        ('client_addr', 'wal_client_addr'),
+        ('replication.application_name', 'wal_app_name'),
+        ('replication.state', 'wal_state'),
+        ('replication.sync_state', 'wal_sync_state'),
+        ('replication.client_addr', 'wal_client_addr'),
+        ('replication_slot.slot_name', 'slot_name'),
+        ('replication_slot.slot_type', 'slot_type'),
     ],
     'metrics': {
-        'GREATEST (0, EXTRACT(epoch from write_lag)) as write_lag': (
-            'replication.wal_write_lag',
+        'GREATEST (0, EXTRACT(epoch from replication.write_lag)) as write_lag': (
+            'wal_write_lag',
path: postgres/datadog_checks/postgres/util.py
user: login: bonnefoa
body: This is used as the metric name so you need to keep the `replication.` prefix. Otherwise, the metric will move from `postgres.replication.wal_write_lag` to `postgres.wal_write_lag`

created_at: 2025-04-08T15:35:09Z
author_association: COLLABORATOR
diff_hunk: @@ -383,33 +383,43 @@ def get_list_chunks(lst, n):
 # Requires postgres 10+
 REPLICATION_STATS_METRICS = {
     'descriptors': [
-        ('application_name', 'wal_app_name'),
-        ('state', 'wal_state'),
-        ('sync_state', 'wal_sync_state'),
-        ('client_addr', 'wal_client_addr'),
+        ('replication.application_name', 'wal_app_name'),
+        ('replication.state', 'wal_state'),
+        ('replication.sync_state', 'wal_sync_state'),
+        ('replication.client_addr', 'wal_client_addr'),
+        ('replication_slot.slot_name', 'slot_name'),
+        ('replication_slot.slot_type', 'slot_type'),
     ],
     'metrics': {
-        'GREATEST (0, EXTRACT(epoch from write_lag)) as write_lag': (
-            'replication.wal_write_lag',
+        'GREATEST (0, EXTRACT(epoch from replication.write_lag)) as write_lag': (
+            'wal_write_lag',
             AgentCheck.gauge,
         ),
-        'GREATEST (0, EXTRACT(epoch from flush_lag)) AS flush_lag': (
-            'replication.wal_flush_lag',
+        'GREATEST (0, EXTRACT(epoch from replication.flush_lag)) AS flush_lag': (
+            'wal_flush_lag',
             AgentCheck.gauge,
         ),
-        'GREATEST (0, EXTRACT(epoch from replay_lag)) AS replay_lag': (
-            'replication.wal_replay_lag',
+        'GREATEST (0, EXTRACT(epoch from replication.replay_lag)) AS replay_lag': (
+            'wal_replay_lag',
             AgentCheck.gauge,
         ),
-        'GREATEST (0, age(backend_xmin)) as backend_xmin_age': (
-            'replication.backend_xmin_age',
+        'GREATEST (0, age(replication.backend_xmin)) as backend_xmin_age': (
+            'backend_xmin_age',
             AgentCheck.gauge,
         ),
     },
     'relation': False,
     'query': """
-SELECT application_name, state, sync_state, client_addr, {metrics_columns}
-FROM pg_stat_replication
+SELECT replication.application_name,
+replication.state,
+replication.sync_state,
+replication.client_addr,
+{metrics_columns},
+replication_slot.slot_name,
+replication_slot.slot_type,
path: postgres/datadog_checks/postgres/util.py
user: login: bonnefoa
body: You have an excessive `,` here
created_at: 2025-04-08T15:38:20Z
author_association: COLLABORATOR
diff_hunk: @@ -383,33 +383,43 @@ def get_list_chunks(lst, n):
 # Requires postgres 10+
 REPLICATION_STATS_METRICS = {
     'descriptors': [
-        ('application_name', 'wal_app_name'),
-        ('state', 'wal_state'),
-        ('sync_state', 'wal_sync_state'),
-        ('client_addr', 'wal_client_addr'),
+        ('replication.application_name', 'wal_app_name'),
+        ('replication.state', 'wal_state'),
+        ('replication.sync_state', 'wal_sync_state'),
+        ('replication.client_addr', 'wal_client_addr'),
+        ('replication_slot.slot_name', 'slot_name'),
+        ('replication_slot.slot_type', 'slot_type'),
     ],
     'metrics': {
-        'GREATEST (0, EXTRACT(epoch from write_lag)) as write_lag': (
-            'replication.wal_write_lag',
+        'GREATEST (0, EXTRACT(epoch from replication.write_lag)) as write_lag': (
+            'wal_write_lag',
             AgentCheck.gauge,
         ),
-        'GREATEST (0, EXTRACT(epoch from flush_lag)) AS flush_lag': (
-            'replication.wal_flush_lag',
+        'GREATEST (0, EXTRACT(epoch from replication.flush_lag)) AS flush_lag': (
+            'wal_flush_lag',
             AgentCheck.gauge,
         ),
-        'GREATEST (0, EXTRACT(epoch from replay_lag)) AS replay_lag': (
-            'replication.wal_replay_lag',
+        'GREATEST (0, EXTRACT(epoch from replication.replay_lag)) AS replay_lag': (
+            'wal_replay_lag',
             AgentCheck.gauge,
         ),
-        'GREATEST (0, age(backend_xmin)) as backend_xmin_age': (
-            'replication.backend_xmin_age',
+        'GREATEST (0, age(replication.backend_xmin)) as backend_xmin_age': (
+            'backend_xmin_age',
             AgentCheck.gauge,
         ),
     },
     'relation': False,
     'query': """
-SELECT application_name, state, sync_state, client_addr, {metrics_columns}
-FROM pg_stat_replication
+SELECT replication.application_name,
+replication.state,
+replication.sync_state,
+replication.client_addr,
+{metrics_columns},
path: postgres/datadog_checks/postgres/util.py
user: login: bonnefoa
body: Order of columns is important here. You want the `metrics_columns` to be last as the first columns are used for the tags listed in `descriptors`.
created_at: 2025-04-08T15:41:25Z
author_association: COLLABORATOR
diff_hunk: @@ -383,33 +383,43 @@ def get_list_chunks(lst, n):
 # Requires postgres 10+
 REPLICATION_STATS_METRICS = {
     'descriptors': [
-        ('application_name', 'wal_app_name'),
-        ('state', 'wal_state'),
-        ('sync_state', 'wal_sync_state'),
-        ('client_addr', 'wal_client_addr'),
+        ('replication.application_name', 'wal_app_name'),
+        ('replication.state', 'wal_state'),
+        ('replication.sync_state', 'wal_sync_state'),
+        ('replication.client_addr', 'wal_client_addr'),
+        ('replication_slot.slot_name', 'slot_name'),
+        ('replication_slot.slot_type', 'slot_type'),
     ],
     'metrics': {
-        'GREATEST (0, EXTRACT(epoch from write_lag)) as write_lag': (
-            'replication.wal_write_lag',
+        'GREATEST (0, EXTRACT(epoch from replication.write_lag)) as write_lag': (
+            'wal_write_lag',
path: postgres/datadog_checks/postgres/util.py
user: login: shreesampath
body:  👍 thank you. 
created_at: 2025-04-09T14:24:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -26,7 +26,7 @@ def __init__(
         super(PostgresAutodiscovery, self).__init__(
             self._get_databases,
             # parent class asks for includelist to be a dictionary
-            include={db: 0 for db in autodiscovery_config.get("include", [".*"])},
+            include=dict.fromkeys(autodiscovery_config.get("include", [".*"]), 0),
path: postgres/datadog_checks/postgres/discovery.py
user: login: shreesampath
body: I ran `ruff check --fix` that made this change.
created_at: 2025-04-09T15:53:28Z
author_association: CONTRIBUTOR

PR comments:
user: login: codecov[bot]
created_at: 2025-04-08T15:43:43Z
body: ## [Codecov](https://app.codecov.io/gh/DataDog/integrations-core/pull/20035?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 86.46%. Comparing base [(`5682c48`)](https://app.codecov.io/gh/DataDog/integrations-core/commit/5682c489b6b11d517c71a9e88ea9631a98f1d1be?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog) to head [(`97f8dfb`)](https://app.codecov.io/gh/DataDog/integrations-core/commit/97f8dfb98f9c5e8e5510daec3a5c007cf36a0363?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog).
> Report is 11 commits behind head on master.

<details><summary>Additional details and impacted files</summary>


| [Flag](https://app.codecov.io/gh/DataDog/integrations-core/pull/20035/flags?src=pr&el=flags&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog) | Coverage Δ | |
|---|---|---|
| [activemq](https://app.codecov.io/gh/DataDog/integrations-core/pull/20035/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog) | `?` | |
| [cassandra](https://app.codecov.io/gh/DataDog/integrations-core/pull/20035/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog) | `?` | |
| [hive](https://app.codecov.io/gh/DataDog/integrations-core/pull/20035/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog) | `?` | |
| [hivemq](https://app.codecov.io/gh/DataDog/integrations-core/pull/20035/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog) | `?` | |
| [hudi](https://app.codecov.io/gh/DataDog/integrations-core/pull/20035/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog) | `?` | |
| [ignite](https://app.codecov.io/gh/DataDog/integrations-core/pull/20035/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog) | `?` | |
| [jboss_wildfly](https://app.codecov.io/gh/DataDog/integrations-core/pull/20035/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog) | `?` | |
| [kafka](https://app.codecov.io/gh/DataDog/integrations-core/pull/20035/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog) | `?` | |
| [postgres](https://app.codecov.io/gh/DataDog/integrations-core/pull/20035/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog) | `93.27% <100.00%> (?)` | |
| [presto](https://app.codecov.io/gh/DataDog/integrations-core/pull/20035/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog) | `?` | |
| [solr](https://app.codecov.io/gh/DataDog/integrations-core/pull/20035/flags?src=pr&el=flag&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog) | `?` | |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=DataDog#carryforward-flags-in-the-pull-request-comment) to find out more.

</details>
<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
- :package: [JS Bundle Analysis](https://docs.codecov.com/docs/javascript-bundle-analysis): Save yourself from yourself by tracking and limiting bundle sizes in JS merges.
</details>
author_association: NONE
user: login: bonnefoa
created_at: 2025-04-09T07:56:17Z
body: Better version of the test:
```
    logical_instance_ip = None
    conn = _get_superconn(pg_instance)
    with conn.cursor() as cur:
        cur.execute("SELECT client_addr FROM pg_stat_replication WHERE application_name='subscription_cities'")
        logical_instance_ip = cur.fetchall()[0][0]

    expected_rep_stats_tags = base_tags + [
        'wal_app_name:subscription_cities',
        'wal_state:streaming',
        'wal_sync_state:async',
        f'wal_client_addr:{logical_instance_ip}',
        'slot_name:subscription_cities',
        'slot_type:logical',
    ]
    assert_metric_at_least(
        aggregator,
        'postgresql.replication.wal_write_lag',
        lower_bound=0,
        tags=expected_rep_stats_tags,
        count=1,
    )
```
And while we add tags from replication_slots, the plugin could be an interesting information.
author_association: COLLABORATOR