Skip to content

Commit 5abad5d

Browse files
feat(tools/postgres): add long_running_transactions, list_locks and replication_stats tools (#1751)
Adds the following tools for Postgre (1) long_running_transactions - reports transactions that exceed a configured duration threshold. (2) list_locks - lists active locks in the database, including the associated process, lock type, relation, mode, and the query holding or waiting on the lock. (3) replication_stats - reports replication-related metrics for WAL streaming replicas, including lag sizes presented in human-readable form. <img width="3020" height="1420" alt="image" src="https://github.com/user-attachments/assets/e7d70063-b90c-4464-90ec-1bd810c02cac" /> <img width="3036" height="1374" alt="image" src="https://github.com/user-attachments/assets/f7cf584b-ae01-455c-9c9c-acca3166a549" /> <img width="2682" height="868" alt="image" src="https://github.com/user-attachments/assets/dd10646c-4521-4d8f-a111-760d6eb01249" /> ## PR Checklist - [Y] Make sure you reviewed [CONTRIBUTING.md](https://github.com/googleapis/genai-toolbox/blob/main/CONTRIBUTING.md) - [Y] Make sure to open an issue as a [bug/issue](https://github.com/googleapis/genai-toolbox/issues/new/choose) before writing your code! That way we can discuss the change, evaluate designs, and agree on the general idea - [Y] Ensure the tests and linter pass - [Y] Code coverage does not decrease (if any source code was changed) - [Y] Appropriate docs were updated (if necessary) - [Y] Make sure to add `!` if this involve a breaking change 🛠️ Fixes #1691 #1715 --------- Co-authored-by: Wenxin Du <[email protected]>
1 parent eddf1a3 commit 5abad5d

22 files changed

+1470
-3
lines changed

cmd/root.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,11 +183,14 @@ import (
183183
_ "github.com/googleapis/genai-toolbox/internal/tools/postgres/postgreslistavailableextensions"
184184
_ "github.com/googleapis/genai-toolbox/internal/tools/postgres/postgreslistindexes"
185185
_ "github.com/googleapis/genai-toolbox/internal/tools/postgres/postgreslistinstalledextensions"
186+
_ "github.com/googleapis/genai-toolbox/internal/tools/postgres/postgreslistlocks"
186187
_ "github.com/googleapis/genai-toolbox/internal/tools/postgres/postgreslistschemas"
187188
_ "github.com/googleapis/genai-toolbox/internal/tools/postgres/postgreslistsequences"
188189
_ "github.com/googleapis/genai-toolbox/internal/tools/postgres/postgreslisttables"
189190
_ "github.com/googleapis/genai-toolbox/internal/tools/postgres/postgreslisttriggers"
190191
_ "github.com/googleapis/genai-toolbox/internal/tools/postgres/postgreslistviews"
192+
_ "github.com/googleapis/genai-toolbox/internal/tools/postgres/postgreslongrunningtransactions"
193+
_ "github.com/googleapis/genai-toolbox/internal/tools/postgres/postgresreplicationstats"
191194
_ "github.com/googleapis/genai-toolbox/internal/tools/postgres/postgressql"
192195
_ "github.com/googleapis/genai-toolbox/internal/tools/redis"
193196
_ "github.com/googleapis/genai-toolbox/internal/tools/serverlessspark/serverlesssparkcancelbatch"

cmd/root_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1478,7 +1478,7 @@ func TestPrebuiltTools(t *testing.T) {
14781478
wantToolset: server.ToolsetConfigs{
14791479
"alloydb_postgres_database_tools": tools.ToolsetConfig{
14801480
Name: "alloydb_postgres_database_tools",
1481-
ToolNames: []string{"execute_sql", "list_tables", "list_active_queries", "list_available_extensions", "list_installed_extensions", "list_autovacuum_configurations", "list_memory_configurations", "list_top_bloated_tables", "list_replication_slots", "list_invalid_indexes", "get_query_plan", "list_views", "list_schemas", "database_overview", "list_triggers", "list_indexes", "list_sequences"},
1481+
ToolNames: []string{"execute_sql", "list_tables", "list_active_queries", "list_available_extensions", "list_installed_extensions", "list_autovacuum_configurations", "list_memory_configurations", "list_top_bloated_tables", "list_replication_slots", "list_invalid_indexes", "get_query_plan", "list_views", "list_schemas", "database_overview", "list_triggers", "list_indexes", "list_sequences", "long_running_transactions", "list_locks", "replication_stats"},
14821482
},
14831483
},
14841484
},
@@ -1508,7 +1508,7 @@ func TestPrebuiltTools(t *testing.T) {
15081508
wantToolset: server.ToolsetConfigs{
15091509
"cloud_sql_postgres_database_tools": tools.ToolsetConfig{
15101510
Name: "cloud_sql_postgres_database_tools",
1511-
ToolNames: []string{"execute_sql", "list_tables", "list_active_queries", "list_available_extensions", "list_installed_extensions", "list_autovacuum_configurations", "list_memory_configurations", "list_top_bloated_tables", "list_replication_slots", "list_invalid_indexes", "get_query_plan", "list_views", "list_schemas", "database_overview", "list_triggers", "list_indexes", "list_sequences"},
1511+
ToolNames: []string{"execute_sql", "list_tables", "list_active_queries", "list_available_extensions", "list_installed_extensions", "list_autovacuum_configurations", "list_memory_configurations", "list_top_bloated_tables", "list_replication_slots", "list_invalid_indexes", "get_query_plan", "list_views", "list_schemas", "database_overview", "list_triggers", "list_indexes", "list_sequences", "long_running_transactions", "list_locks", "replication_stats"},
15121512
},
15131513
},
15141514
},
@@ -1608,7 +1608,7 @@ func TestPrebuiltTools(t *testing.T) {
16081608
wantToolset: server.ToolsetConfigs{
16091609
"postgres_database_tools": tools.ToolsetConfig{
16101610
Name: "postgres_database_tools",
1611-
ToolNames: []string{"execute_sql", "list_tables", "list_active_queries", "list_available_extensions", "list_installed_extensions", "list_autovacuum_configurations", "list_memory_configurations", "list_top_bloated_tables", "list_replication_slots", "list_invalid_indexes", "get_query_plan", "list_views", "list_schemas", "database_overview", "list_triggers", "list_indexes", "list_sequences"},
1611+
ToolNames: []string{"execute_sql", "list_tables", "list_active_queries", "list_available_extensions", "list_installed_extensions", "list_autovacuum_configurations", "list_memory_configurations", "list_top_bloated_tables", "list_replication_slots", "list_invalid_indexes", "get_query_plan", "list_views", "list_schemas", "database_overview", "list_triggers", "list_indexes", "list_sequences", "long_running_transactions", "list_locks", "replication_stats"},
16121612
},
16131613
},
16141614
},

docs/en/resources/sources/alloydb-pg.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,14 @@ cluster][alloydb-free-trial].
6262

6363
- [`postgres-list-sequences`](../tools/postgres/postgres-list-sequences.md)
6464
List sequences in a PostgreSQL database.
65+
- [`postgres-long-running-transactions`](../tools/postgres/postgres-long-running-transactions.md)
66+
List long running transactions in a PostgreSQL database.
67+
68+
- [`postgres-list-locks`](../tools/postgres/postgres-list-locks.md)
69+
List lock stats in a PostgreSQL database.
70+
71+
- [`postgres-replication-stats`](../tools/postgres/postgres-replication-stats.md)
72+
List replication stats in a PostgreSQL database.
6573

6674
### Pre-built Configurations
6775

docs/en/resources/sources/cloud-sql-pg.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@ to a database by following these instructions][csql-pg-quickstart].
5858

5959
- [`postgres-list-sequences`](../tools/postgres/postgres-list-sequences.md)
6060
List sequences in a PostgreSQL database.
61+
- [`postgres-long-running-transactions`](../tools/postgres/postgres-long-running-transactions.md)
62+
List long running transactions in a PostgreSQL database.
63+
64+
- [`postgres-list-locks`](../tools/postgres/postgres-list-locks.md)
65+
List lock stats in a PostgreSQL database.
66+
67+
- [`postgres-replication-stats`](../tools/postgres/postgres-replication-stats.md)
68+
List replication stats in a PostgreSQL database.
6169

6270
### Pre-built Configurations
6371

docs/en/resources/sources/postgres.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,15 @@ reputation for reliability, feature robustness, and performance.
5353
- [`postgres-list-sequences`](../tools/postgres/postgres-list-sequences.md)
5454
List sequences in a PostgreSQL database.
5555

56+
- [`postgres-long-running-transactions`](../tools/postgres/postgres-long-running-transactions.md)
57+
List long running transactions in a PostgreSQL database.
58+
59+
- [`postgres-list-locks`](../tools/postgres/postgres-list-locks.md)
60+
List lock stats in a PostgreSQL database.
61+
62+
- [`postgres-replication-stats`](../tools/postgres/postgres-replication-stats.md)
63+
List replication stats in a PostgreSQL database.
64+
5665
### Pre-built Configurations
5766

5867
- [PostgreSQL using MCP](https://googleapis.github.io/genai-toolbox/how-to/connect-ide/postgres_mcp/)
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
---
2+
title: "postgres-list-locks"
3+
type: docs
4+
weight: 1
5+
description: >
6+
The "postgres-list-locks" tool lists active locks in the database, including the associated process, lock type, relation, mode, and the query holding or waiting on the lock.
7+
aliases:
8+
- /resources/tools/postgres-list-locks
9+
---
10+
11+
## About
12+
13+
The `postgres-list-locks` tool displays information about active locks by joining pg_stat_activity with pg_locks. This is useful to find transactions holding or waiting for locks and to troubleshoot contention.
14+
15+
Compatible sources:
16+
17+
- [alloydb-postgres](../../sources/alloydb-pg.md)
18+
- [cloud-sql-postgres](../../sources/cloud-sql-pg.md)
19+
- [postgres](../../sources/postgres.md)
20+
21+
22+
This tool identifies all locks held by active processes showing the process ID, user, query text, and an aggregated list of all transactions and specific locks (relation, mode, grant status) associated with each process.
23+
24+
## Query
25+
26+
The tool aggregates locks per backend (process) and returns the concatenated transaction ids and lock entries. The SQL used by the tool looks like:
27+
28+
```sql
29+
SELECT
30+
locked.pid,
31+
locked.usename,
32+
locked.query,
33+
string_agg(locked.transactionid::text,':') as trxid,
34+
string_agg(locked.lockinfo,'||') as locks
35+
FROM
36+
(SELECT
37+
a.pid,
38+
a.usename,
39+
a.query,
40+
l.transactionid,
41+
(l.granted::text||','||coalesce(l.relation::regclass,0)::text||','||l.mode::text)::text as lockinfo
42+
FROM
43+
pg_stat_activity a
44+
JOIN pg_locks l ON l.pid = a.pid AND a.pid != pg_backend_pid()) as locked
45+
GROUP BY
46+
locked.pid, locked.usename, locked.query;
47+
```
48+
49+
## Example
50+
51+
```yaml
52+
tools:
53+
list_locks:
54+
kind: postgres-list-locks
55+
source: postgres-source
56+
description: "Lists active locks with associated process and query information."
57+
```
58+
59+
Example response element (aggregated per process):
60+
61+
```json
62+
{
63+
"pid": 23456,
64+
"usename": "dbuser",
65+
"query": "INSERT INTO orders (...) VALUES (...);",
66+
"trxid": "12345:0",
67+
"locks": "true,public.orders,RowExclusiveLock||false,0,ShareUpdateExclusiveLock"
68+
}
69+
```
70+
71+
## Reference
72+
73+
| field | type | required | description |
74+
|:--------|:--------|:--------:|:------------|
75+
| pid | integer | true | Process id (backend pid). |
76+
| usename | string | true | Database user. |
77+
| query | string | true | SQL text associated with the session. |
78+
| trxid | string | true | Aggregated transaction ids for the process, joined by ':' (string). Each element is the transactionid as text. |
79+
| locks | string | true | Aggregated lock info entries for the process, joined by '||'. Each entry is a comma-separated triple: `granted,relation,mode` where `relation` may be `0` when not resolvable via regclass. |
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
---
2+
title: "postgres-long-running-transactions"
3+
type: docs
4+
weight: 1
5+
description: >
6+
The postgres-long-running-transactions tool Identifies and lists database transactions that exceed a specified time limit. For each of the long running transactions, the output contains the process id, database name, user name, application name, client address, state, connection age, transaction age, query age, last activity age, wait event type, wait event, and query string.
7+
aliases:
8+
- /resources/tools/postgres-long-running-transactions
9+
---
10+
11+
## About
12+
13+
The `postgres-long-running-transactions` tool reports transactions that exceed a configured duration threshold by scanning `pg_stat_activity` for sessions where `xact_start` is set and older than the configured interval.
14+
15+
Compatible sources:
16+
17+
- [alloydb-postgres](../../sources/alloydb-pg.md)
18+
- [cloud-sql-postgres](../../sources/cloud-sql-pg.md)
19+
- [postgres](../../sources/postgres.md)
20+
21+
The tool returns a JSON array with one object per matching session (non-idle). Each object contains the process id, database and user, application name, client address, session state, several age intervals (connection, transaction, query, and last activity), wait event info, and the SQL text currently associated with the session.
22+
23+
Parameters:
24+
25+
- `min_duration` (optional): Only show transactions running at least this long (Postgres interval format, e.g., '5 minutes'). Default: `5 minutes`.
26+
- `limit` (optional): Maximum number of results to return. Default: `20`.
27+
28+
## Query
29+
30+
The SQL used by the tool looks like:
31+
32+
```sql
33+
SELECT
34+
pid,
35+
datname,
36+
usename,
37+
application_name as appname,
38+
client_addr,
39+
state,
40+
now() - backend_start as conn_age,
41+
now() - xact_start as xact_age,
42+
now() - query_start as query_age,
43+
now() - state_change as last_activity_age,
44+
wait_event_type,
45+
wait_event,
46+
query
47+
FROM
48+
pg_stat_activity
49+
WHERE
50+
state <> 'idle'
51+
AND (now() - xact_start) > COALESCE($1::INTERVAL, interval '5 minutes')
52+
AND xact_start IS NOT NULL
53+
AND pid <> pg_backend_pid()
54+
ORDER BY
55+
xact_age DESC
56+
LIMIT
57+
COALESCE($2::int, 20);
58+
```
59+
60+
## Example
61+
62+
```yaml
63+
tools:
64+
long_running_transactions:
65+
kind: postgres-long-running-transactions
66+
source: postgres-source
67+
description: "Identifies transactions open longer than a threshold and returns details including query text and durations."
68+
```
69+
70+
Example response element:
71+
72+
```json
73+
{
74+
"pid": 12345,
75+
"datname": "my_database",
76+
"usename": "dbuser",
77+
"appname": "my_app",
78+
"client_addr": "10.0.0.5",
79+
"state": "idle in transaction",
80+
"conn_age": "00:12:34",
81+
"xact_age": "00:06:00",
82+
"query_age": "00:02:00",
83+
"last_activity_age": "00:01:30",
84+
"wait_event_type": null,
85+
"wait_event": null,
86+
"query": "UPDATE users SET last_seen = now() WHERE id = 42;"
87+
}
88+
```
89+
90+
## Reference
91+
92+
| field | type | required | description |
93+
|:---------------------|:--------|:--------:|:------------|
94+
| pid | integer | true | Process id (backend pid). |
95+
| datname | string | true | Database name. |
96+
| usename | string | true | Database user name. |
97+
| appname | string | false | Application name (client application). |
98+
| client_addr | string | false | Client IPv4/IPv6 address (may be null for local connections). |
99+
| state | string | true | Session state (e.g., active, idle in transaction). |
100+
| conn_age | string | true | Age of the connection: `now() - backend_start` (Postgres interval serialized as string). |
101+
| xact_age | string | true | Age of the transaction: `now() - xact_start` (Postgres interval serialized as string). |
102+
| query_age | string | true | Age of the currently running query: `now() - query_start` (Postgres interval serialized as string). |
103+
| last_activity_age | string | true | Time since last state change: `now() - state_change` (Postgres interval serialized as string). |
104+
| wait_event_type | string | false | Type of event the backend is waiting on (may be null). |
105+
| wait_event | string | false | Specific wait event name (may be null). |
106+
| query | string | true | SQL text associated with the session. |
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
---
2+
title: "postgres-replication-stats"
3+
type: docs
4+
weight: 1
5+
description: >
6+
The "postgres-replication-stats" tool reports replication-related metrics for WAL streaming replicas, including lag sizes presented in human-readable form.
7+
aliases:
8+
- /resources/tools/postgres-replication-stats
9+
---
10+
11+
## About
12+
13+
The `postgres-replication-stats` tool queries pg_stat_replication to surface the status of connected replicas. It reports application_name, client address, connection and sync state, and human-readable lag sizes (sent, write, flush, replay, and total) computed using WAL LSN differences.
14+
15+
Compatible sources:
16+
17+
- [alloydb-postgres](../../sources/alloydb-pg.md)
18+
- [cloud-sql-postgres](../../sources/cloud-sql-pg.md)
19+
- [postgres](../../sources/postgres.md)
20+
21+
This tool takes no parameters. It returns a JSON array; each element represents a replication connection on the primary and includes lag metrics formatted by pg_size_pretty.
22+
23+
## Example
24+
25+
```yaml
26+
tools:
27+
replication_stats:
28+
kind: postgres-replication-stats
29+
source: postgres-source
30+
description: "Lists replication connections and readable WAL lag metrics."
31+
```
32+
33+
Example response element:
34+
35+
```json
36+
{
37+
"pid": 12345,
38+
"usename": "replication_user",
39+
"application_name": "replica-1",
40+
"backend_xmin": "0/0",
41+
"client_addr": "10.0.0.7",
42+
"state": "streaming",
43+
"sync_state": "sync",
44+
"sent_lag": "1234 kB",
45+
"write_lag": "12 kB",
46+
"flush_lag": "0 bytes",
47+
"replay_lag": "0 bytes",
48+
"total_lag": "1234 kB"
49+
}
50+
```
51+
52+
## Reference
53+
54+
| field | type | required | description |
55+
|------------------:|:-------:|:--------:|:------------|
56+
| pid | integer | true | Process ID of the replication backend on the primary. |
57+
| usename | string | true | Name of the user performing the replication connection. |
58+
| application_name | string | true | Name of the application (replica) connecting to the primary. |
59+
| backend_xmin | string | false | Standby's xmin horizon reported by hot_standby_feedback (may be null). |
60+
| client_addr | string | false | Client IP address of the replica (may be null). |
61+
| state | string | true | Connection state (e.g., streaming). |
62+
| sync_state | string | true | Sync state (e.g., async, sync, potential). |
63+
| sent_lag | string | true | Human-readable size difference between current WAL LSN and sent_lsn. |
64+
| write_lag | string | true | Human-readable write lag between sent_lsn and write_lsn. |
65+
| flush_lag | string | true | Human-readable flush lag between write_lsn and flush_lsn. |
66+
| replay_lag | string | true | Human-readable replay lag between flush_lsn and replay_lsn. |
67+
| total_lag | string | true | Human-readable total lag between current WAL LSN and replay_lsn. |

internal/prebuiltconfigs/tools/alloydb-postgres.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,18 @@ tools:
5050
source: alloydb-pg-source
5151
description: "List all installed PostgreSQL extensions with their name, version, schema, owner, and description."
5252

53+
long_running_transactions:
54+
kind: postgres-long-running-transactions
55+
source: alloydb-pg-source
56+
57+
list_locks:
58+
kind: postgres-list-locks
59+
source: alloydb-pg-source
60+
61+
replication_stats:
62+
kind: postgres-replication-stats
63+
source: alloydb-pg-source
64+
5365
list_autovacuum_configurations:
5466
kind: postgres-sql
5567
source: alloydb-pg-source
@@ -199,3 +211,7 @@ toolsets:
199211
- list_triggers
200212
- list_indexes
201213
- list_sequences
214+
- long_running_transactions
215+
- list_locks
216+
- replication_stats
217+

internal/prebuiltconfigs/tools/cloud-sql-postgres.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,21 @@ tools:
4949
source: cloudsql-pg-source
5050
description: "List all installed PostgreSQL extensions with their name, version, schema, owner, and description."
5151

52+
long_running_transactions:
53+
kind: postgres-long-running-transactions
54+
source: cloudsql-pg-source
55+
description: "Identifies and lists database transactions that exceed a specified time limit. For each of the long running transactions, the output contains the process id, database name, user name, application name, client address, state, connection age, transaction age, query age, last activity age, wait event type, wait event, and query string."
56+
57+
list_locks:
58+
kind: postgres-list-locks
59+
source: cloudsql-pg-source
60+
description: "Identifies all locks held by active processes showing the process ID, user, query text, and an aggregated list of all transactions and specific locks (relation, mode, grant status) associated with each process."
61+
62+
replication_stats:
63+
kind: postgres-replication-stats
64+
source: cloudsql-pg-source
65+
description: "Lists each replica's process ID, user name, application name, backend_xmin (standby's xmin horizon reported by hot_standby_feedback), client IP address, connection state, and sync_state, along with lag sizes in bytes for sent_lag (primary to sent), write_lag (sent to written), flush_lag (written to flushed), replay_lag (flushed to replayed), and the overall total_lag (primary to replayed)."
66+
5267
list_autovacuum_configurations:
5368
kind: postgres-sql
5469
source: cloudsql-pg-source
@@ -198,3 +213,6 @@ toolsets:
198213
- list_triggers
199214
- list_indexes
200215
- list_sequences
216+
- long_running_transactions
217+
- list_locks
218+
- replication_stats

0 commit comments

Comments
 (0)