Skip to content

Commit

Permalink
Add the xmin horizon to running query display
Browse files Browse the repository at this point in the history
The xmin horizon was added in 9.4 in commit [1].
[1] postgres/postgres@dd1a3bc
  • Loading branch information
blogh committed Feb 18, 2025
1 parent 15bffac commit 681a9c5
Show file tree
Hide file tree
Showing 26 changed files with 166 additions and 16 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
configuration.
* Add a `y` command to copy focused query to the system clipboard, using
OSC 52 escape sequence (#311).
* Add the `xmin` column to the query display (#425).

### Fixed

Expand Down
7 changes: 7 additions & 0 deletions docs/man/pg_activity.1
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@ The running queries panel shows all running queries, transactions or backends
.IP "\- \fBPID\fR: process id of the backend which executes the query;" 2
.IX Item "- PID: process id of the backend which executes the query;"
.PD 0
.IP "\- \fBXMIN\fR: xmin horizon of the backend;" 2
.IX Item "- XMIN: xmin horizon of the backend;"
.IP "\- \fBDATABASE\fR: database specified in the connection string;" 2
.IX Item "- DATABASE: database specified in the connection string;"
.IP "\- \fBAPP\fR: application name specified in the connection string;" 2
Expand Down Expand Up @@ -364,6 +366,11 @@ required by another session. It shows following information:
.Vb 1
\& Enable/disable PID.
.Ve
.IP "\fB\-\-xmin\fR, \fB\-\-no\-xmin\fR" 2
.IX Item "--xmin, --no-xmin"
.Vb 1
\& Enable/disable XMIN.
.Ve
.IP "\fB\-\-database\fR, \fB\-\-no\-database\fR" 2
.IX Item "--database, --no-database"
.Vb 1
Expand Down
6 changes: 6 additions & 0 deletions docs/man/pg_activity.pod
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ B<min duration> seconds. It displays the following information:

=item - B<PID>: process id of the backend which executes the query;

=item - B<XMIN>: xmin horizon of the backend;

=item - B<DATABASE>: database specified in the connection string;

=item - B<APP>: application name specified in the connection string;
Expand Down Expand Up @@ -322,6 +324,10 @@ required by another session. It shows following information:

Enable/disable PID.

=item B<--xmin>, B<--no-xmin>

Enable/disable XMIN.

=item B<--database>, B<--no-database>

Enable/disable DATABASE.
Expand Down
6 changes: 6 additions & 0 deletions pgactivity/activities.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ def sorted(processes: list[T], *, key: SortKey, reverse: bool = False) -> list[T
>>> processes = [
... LocalRunningProcess(
... pid="6240",
... xmin="1234",
... application_name="pgbench",
... database="pgbench",
... user="postgres",
Expand All @@ -170,6 +171,7 @@ def sorted(processes: list[T], *, key: SortKey, reverse: bool = False) -> list[T
... ),
... LocalRunningProcess(
... pid="6239",
... xmin="2345",
... application_name="pgbench",
... database="pgbench",
... user="postgres",
Expand All @@ -189,6 +191,7 @@ def sorted(processes: list[T], *, key: SortKey, reverse: bool = False) -> list[T
... ),
... LocalRunningProcess(
... pid="6228",
... xmin="3456",
... application_name="pgbench",
... database="pgbench",
... user="postgres",
Expand Down Expand Up @@ -225,6 +228,7 @@ def sorted(processes: list[T], *, key: SortKey, reverse: bool = False) -> list[T
>>> processes = [
... LocalRunningProcess(
... pid="6240",
... xmin="1234",
... application_name="pgbench",
... database="pgbench",
... user="postgres",
Expand All @@ -244,6 +248,7 @@ def sorted(processes: list[T], *, key: SortKey, reverse: bool = False) -> list[T
... ),
... LocalRunningProcess(
... pid="6239",
... xmin="2345",
... application_name="pgbench",
... database="pgbench",
... user="postgres",
Expand All @@ -263,6 +268,7 @@ def sorted(processes: list[T], *, key: SortKey, reverse: bool = False) -> list[T
... ),
... LocalRunningProcess(
... pid="6228",
... xmin="3456",
... application_name="pgbench",
... database="pgbench",
... user="postgres",
Expand Down
1 change: 1 addition & 0 deletions pgactivity/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ def get_parser() -> argparse.ArgumentParser:
"These options may be used hide some columns from the processes table.",
)
flag(group, "--pid", dest="pid", feature="PID")
flag(group, "--xmin", dest="xmin", feature="XMIN")
flag(group, "--database", dest="database", feature="DATABASE")
flag(group, "--user", dest="user", feature="USER")
flag(group, "--client", dest="client", feature="CLIENT")
Expand Down
7 changes: 5 additions & 2 deletions pgactivity/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ class Flag(enum.Flag):
"""Column flag.
>>> Flag.names()
['database', 'appname', 'client', 'user', 'cpu', 'mem', 'read', 'write', 'time', 'wait', 'relation', 'type', 'mode', 'iowait', 'pid']
['database', 'appname', 'client', 'user', 'cpu', 'mem', 'read', 'write', 'time', 'wait', 'relation', 'type', 'mode', 'iowait', 'pid', 'xmin']
>>> Flag.all() # doctest: +ELLIPSIS
<Flag...: 32767>
<Flag...: 65535>
"""

DATABASE = enum.auto()
Expand All @@ -82,6 +82,7 @@ class Flag(enum.Flag):
MODE = enum.auto()
IOWAIT = enum.auto()
PID = enum.auto()
XMIN = enum.auto()

@classmethod
def names(cls) -> list[str]:
Expand Down Expand Up @@ -131,6 +132,7 @@ def load(
user: bool | None,
wait: bool | None,
write: bool | None,
xmin: bool | None,
**kwargs: Any,
) -> Flag:
"""Build a Flag value from command line options."""
Expand All @@ -150,6 +152,7 @@ def load(
(user, cls.USER),
(wait, cls.WAIT),
(write, cls.WRITE),
(xmin, cls.XMIN),
):
if opt is True:
flag |= value
Expand Down
3 changes: 3 additions & 0 deletions pgactivity/profiles/minimal.conf
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ show_instance = no
show_system = no
show_workers = no

[xmin]
hidden = yes

[database]
hidden = yes

Expand Down
3 changes: 3 additions & 0 deletions pgactivity/profiles/narrow.conf
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[xmin]
hidden = yes

[database]
hidden = yes

Expand Down
3 changes: 3 additions & 0 deletions pgactivity/profiles/wide.conf
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[xmin]
hidden = no

[database]
hidden = no

Expand Down
1 change: 1 addition & 0 deletions pgactivity/queries/get_pg_activity_oldest.sql
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
-- Get data from pg_activity before pg 9.2
SELECT
a.procpid AS pid,
NULL AS xmin,
'<unknown>' AS application_name,
a.datname AS database,
a.client_addr AS client,
Expand Down
1 change: 1 addition & 0 deletions pgactivity/queries/get_pg_activity_post_090200.sql
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
-- NEW pg_stat_activity.procpid => pg_stat_activity.pid
SELECT
a.pid AS pid,
NULL AS xmin,
a.application_name AS application_name,
a.datname AS database,
a.client_addr AS client,
Expand Down
31 changes: 31 additions & 0 deletions pgactivity/queries/get_pg_activity_post_090400.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
-- Get data from pg_activity from pg 9.4 to 9.6
-- NEW pg_stat_activity.backend_xmin
SELECT
a.pid AS pid,
a.backend_xmin AS xmin,
a.application_name AS application_name,
a.datname AS database,
a.client_addr AS client,
EXTRACT(epoch FROM (NOW() - a.{duration_column})) AS duration,
a.wait_event AS wait,
a.usename AS user,
a.state AS state,
a.query AS query,
pg_catalog.pg_encoding_to_char(b.encoding) AS encoding,
NULL AS query_leader_pid,
false AS is_parallel_worker
FROM
pg_stat_activity a
LEFT OUTER JOIN pg_database b ON a.datid = b.oid
WHERE
state <> 'idle'
AND pid <> pg_backend_pid()
AND CASE WHEN {min_duration} = 0
THEN true
ELSE extract(epoch from now() - {duration_column}) > %(min_duration)s
END
AND CASE WHEN {dbname_filter} IS NULL THEN true
ELSE a.datname ~* %(dbname_filter)s
END
ORDER BY
EXTRACT(epoch FROM (NOW() - a.{duration_column})) DESC;
1 change: 1 addition & 0 deletions pgactivity/queries/get_pg_activity_post_090600.sql
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
-- NEW pg_stat_activity.waiting => pg_stat_activity.wait_event
SELECT
a.pid AS pid,
a.backend_xmin AS xmin,
a.application_name AS application_name,
a.datname AS database,
a.client_addr AS client,
Expand Down
1 change: 1 addition & 0 deletions pgactivity/queries/get_pg_activity_post_100000.sql
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
-- NEW pg_activity.backend_type
SELECT
a.pid AS pid,
a.backend_xmin AS xmin,
a.application_name AS application_name,
a.datname AS database,
a.client_addr AS client,
Expand Down
1 change: 1 addition & 0 deletions pgactivity/queries/get_pg_activity_post_110000.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
-- NEW pg_activity.backend_type value for 'parallel worker'
SELECT
a.pid AS pid,
a.backend_xmin AS xmin,
a.application_name AS application_name,
a.datname AS database,
a.client_addr AS client,
Expand Down
1 change: 1 addition & 0 deletions pgactivity/queries/get_pg_activity_post_130000.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
-- NEW pg_activity.leader_pid
SELECT
a.pid AS pid,
a.backend_xmin AS xmin,
a.application_name AS application_name,
a.datname AS database,
a.client_addr AS client,
Expand Down
8 changes: 8 additions & 0 deletions pgactivity/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -465,10 +465,17 @@ def add_column(
sort_key=SortKey.write,
transform=utils.naturalsize,
)
add_column(
Flag.XMIN,
key="xmin",
min_width=8,
default_color="cyan",
)

columns_key_by_querymode: Mapping[QueryMode, list[str]] = {
QueryMode.activities: [
"pid",
"xmin",
"database",
"application_name",
"user",
Expand Down Expand Up @@ -916,6 +923,7 @@ def from_bytes(
class RunningProcess(BaseProcess):
"""Process for a running query."""

xmin: int
wait: bool | None | str
query_leader_pid: int | None
is_parallel_worker: bool
Expand Down
21 changes: 12 additions & 9 deletions pgactivity/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,24 +221,24 @@ def csv_write(
"""Store process list into CSV file.
>>> processes = [
... {'pid': 25199, 'application_name': '', 'database': 'pgbench', 'user': None,
... {'pid': 25199, 'xmin': 1234, 'application_name': '', 'database': 'pgbench', 'user': None,
... 'client': 'local', 'cpu': 0.0, 'mem': 0.6504979545924837,
... 'read': 0.0, 'write': 0.0, 'state': 'active',
... 'query': 'autovacuum: VACUUM ANALYZE public.pgbench_tellers',
... 'duration': 0.348789, 'wait': False,
... 'io_wait': False, 'is_parallel_worker': False},
... {'pid': 25068, 'application_name': 'pgbench', 'database': None,
... {'pid': 25068, 'xmin': 2345, 'application_name': 'pgbench', 'database': None,
... 'user': 'postgres', 'client': 'local', 'cpu': 0.0, 'mem': 2.4694780629380646,
... 'read': 278536.76590087387, 'write': 835610.2977026217,
... 'state': 'idle in transaction',
... 'query': 'INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (625, 87, 4368910, -341, CURRENT_TIMESTAMP);',
... 'duration': 0.000105, 'wait': False, 'io_wait': False,
... 'is_parallel_worker': False},
... {'pid': 25379, 'application_name': 'pgbench', 'database': 'pgbench',
... {'pid': 25379, 'xmin': 3456, 'application_name': 'pgbench', 'database': 'pgbench',
... 'user': 'postgres', 'client': 'local', 'state': 'active',
... 'query': 'UPDATE pgbench_branches SET bbalance = bbalance + -49 WHERE bid = 73;',
... 'duration': 0, 'wait': False},
... {'pid': 25392, 'application_name': 'pgbench', 'database': 'pgbench',
... {'pid': 25392, 'xmin': 4567, 'application_name': 'pgbench', 'database': 'pgbench',
... 'user': 'postgres', 'client': 'local', 'state': 'active',
... 'query': 'BEGIN;', 'duration': 0, 'wait': False}
... ]
Expand All @@ -249,11 +249,11 @@ def csv_write(
... _ = f.seek(0)
... content = f.read()
>>> print(content, end="") # doctest: +ELLIPSIS
datetimeutc;pid;database;appname;user;client;cpu;memory;read;write;duration;wait;io_wait;state;query
"...-...-...T...Z";"25199";"pgbench";"";"None";"local";"0.0";"0.6504979545924837";"0.0";"0.0";"0.348789";"N";"N";"active";"autovacuum: VACUUM ANALYZE public.pgbench_tellers"
"...-...-...T...Z";"25068";"";"pgbench";"postgres";"local";"0.0";"2.4694780629380646";"278536.76590087387";"835610.2977026217";"0.000105";"N";"N";"idle in transaction";"INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (625, 87, 4368910, -341, CURRENT_TIMESTAMP);"
"...-...-...T...Z";"25379";"pgbench";"pgbench";"postgres";"local";"N/A";"N/A";"N/A";"N/A";"0";"N";"N/A";"active";"UPDATE pgbench_branches SET bbalance = bbalance + -49 WHERE bid = 73;"
"...-...-...T...Z";"25392";"pgbench";"pgbench";"postgres";"local";"N/A";"N/A";"N/A";"N/A";"0";"N";"N/A";"active";"BEGIN;"
datetimeutc;pid;xmin;database;appname;user;client;cpu;memory;read;write;duration;wait;io_wait;state;query
"...-...-...T...Z";"25199";"1234";"pgbench";"";"None";"local";"0.0";"0.6504979545924837";"0.0";"0.0";"0.348789";"N";"N";"active";"autovacuum: VACUUM ANALYZE public.pgbench_tellers"
"...-...-...T...Z";"25068";"2345";"";"pgbench";"postgres";"local";"0.0";"2.4694780629380646";"278536.76590087387";"835610.2977026217";"0.000105";"N";"N";"idle in transaction";"INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (625, 87, 4368910, -341, CURRENT_TIMESTAMP);"
"...-...-...T...Z";"25379";"3456";"pgbench";"pgbench";"postgres";"local";"N/A";"N/A";"N/A";"N/A";"0";"N";"N/A";"active";"UPDATE pgbench_branches SET bbalance = bbalance + -49 WHERE bid = 73;"
"...-...-...T...Z";"25392";"4567";"pgbench";"pgbench";"postgres";"local";"N/A";"N/A";"N/A";"N/A";"0";"N";"N/A";"active";"BEGIN;"
"""

def clean_str_csv(s: str) -> str:
Expand All @@ -266,6 +266,7 @@ def clean_str_csv(s: str) -> str:
[
"datetimeutc",
"pid",
"xmin",
"database",
"appname",
"user",
Expand All @@ -292,6 +293,7 @@ def yn_na(value: bool | None) -> str:
for p in procs:
dt = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
pid = p.get("pid", "N/A")
xmin = p.get("xmin", "N/A")
database = p.get("database", "N/A") or ""
appname = p.get("application_name", "N/A")
user = p.get("user", "N/A")
Expand All @@ -310,6 +312,7 @@ def yn_na(value: bool | None) -> str:
[
f'"{dt}"',
f'"{pid}"',
f'"{xmin}"',
f'"{database}"',
f'"{appname}"',
f'"{user}"',
Expand Down
Loading

0 comments on commit 681a9c5

Please sign in to comment.