@@ -1552,8 +1552,36 @@ metrics:
15521552 - total_relation_size_bytes
15531553 statement_timeout_seconds : 15
15541554 pg_stat_all_indexes :
1555+ description : >
1556+ Retrieves index-level scan/tuple counters from `pg_stat_all_indexes`,
1557+ bounded to the top 100 by `idx_scan` per database. Adapts the top-N
1558+ + `'$other$'` bucket pattern from pgwatch2 PostgresAI edition
1559+ (gitlab.com/postgres-ai/pgwatch2 — our fork of Cybertec's pgwatch2):
1560+ everything below the cap is summed into a single `'$other$'` row so
1561+ dashboard totals stay correct under the cap.
1562+ Reads pg_stat_all_indexes (not pg_stat_user_indexes) so pg_catalog,
1563+ pg_toast and _timescaledb_internal indexes stay visible: a hot
1564+ catalog index or a Timescale chunk index ranks in by activity, not
1565+ by schema membership. The `'$other$'` sentinel starts with `$`,
1566+ which is not legal as the first character of an unquoted Postgres
1567+ identifier, so it cannot collide with any real schema, table or
1568+ index name. Compatible with all Postgres versions.
15551569 sqls :
15561570 11 : |
1571+ with ranked as (
1572+ select
1573+ row_number() over (
1574+ order by idx_scan desc nulls last,
1575+ schemaname, relname, indexrelname
1576+ ) as rownum,
1577+ schemaname,
1578+ relname,
1579+ indexrelname,
1580+ idx_scan,
1581+ idx_tup_read,
1582+ idx_tup_fetch
1583+ from pg_stat_all_indexes
1584+ )
15571585 select /* pgwatch_generated */
15581586 current_database() as tag_datname,
15591587 schemaname as tag_schemaname,
@@ -1562,17 +1590,79 @@ metrics:
15621590 idx_scan,
15631591 idx_tup_read,
15641592 idx_tup_fetch
1565- from pg_stat_all_indexes
1566- order by idx_scan desc
1567- limit 5000
1593+ from ranked
1594+ where rownum <= 100
1595+ union all
1596+ select
1597+ current_database() as tag_datname,
1598+ '$other$'::text as tag_schemaname,
1599+ '$other$'::text as tag_relname,
1600+ '$other$'::text as tag_indexrelname,
1601+ coalesce(sum(idx_scan), 0)::int8 as idx_scan,
1602+ coalesce(sum(idx_tup_read), 0)::int8 as idx_tup_read,
1603+ coalesce(sum(idx_tup_fetch), 0)::int8 as idx_tup_fetch
1604+ from ranked
1605+ where rownum > 100
1606+ group by ()
1607+ having count(*) > 0
15681608 gauges :
15691609 - idx_scan
15701610 - idx_tup_read
15711611 - idx_tup_fetch
15721612 statement_timeout_seconds : 15
15731613 pg_stat_all_tables :
1614+ description : >
1615+ Retrieves table-level activity counters from `pg_stat_all_tables`,
1616+ bounded to the top 100 per database. Adapts the top-N + `'$other$'`
1617+ bucket pattern from pgwatch2 PostgresAI edition
1618+ (gitlab.com/postgres-ai/pgwatch2): everything below the cap is summed
1619+ into a single `'$other$'` row so dashboard totals stay correct.
1620+ Reads pg_stat_all_tables (not pg_stat_user_tables) so pg_catalog,
1621+ pg_toast and _timescaledb_internal tables stay visible: a bloated
1622+ TOAST table or a huge Timescale chunk ranks in by size, not by
1623+ schema membership. Ranks by `pg_class.relpages` (8 KiB block count
1624+ cached in the catalog) joined on `relid` instead of calling
1625+ `pg_total_relation_size(relid)` per row: the catalog join is O(1)
1626+ lookup and cannot raise on a concurrently-dropped relation, whereas
1627+ the function opens the relation and toast index every call.
1628+ Large, infrequently-updated tables stay in scope. The `'$other$'`
1629+ sentinel starts with `$`, which is not legal as the first character
1630+ of an unquoted Postgres identifier, so it cannot collide with any
1631+ real schema or table name. `last_vacuum` / `last_analyze` on the
1632+ `'$other$'` row aggregate the most recent maintenance time across
1633+ the tail (not `0`), so dashboards don't render 1970-01-01 when the
1634+ tail has been vacuumed. Compatible with all Postgres versions.
15741635 sqls :
15751636 11 : |
1637+ with ranked as (
1638+ select
1639+ row_number() over (
1640+ order by coalesce(c.relpages, 0) desc nulls last,
1641+ s.schemaname, s.relname
1642+ ) as rownum,
1643+ s.schemaname,
1644+ s.relname,
1645+ s.seq_scan,
1646+ s.seq_tup_read,
1647+ s.idx_scan,
1648+ s.idx_tup_fetch,
1649+ s.n_tup_ins,
1650+ s.n_tup_upd,
1651+ s.n_tup_del,
1652+ s.n_tup_hot_upd,
1653+ s.n_live_tup,
1654+ s.n_dead_tup,
1655+ s.last_vacuum,
1656+ s.last_autovacuum,
1657+ s.last_analyze,
1658+ s.last_autoanalyze,
1659+ s.vacuum_count,
1660+ s.autovacuum_count,
1661+ s.analyze_count,
1662+ s.autoanalyze_count
1663+ from pg_stat_all_tables s
1664+ left join pg_class c on c.oid = s.relid
1665+ )
15761666 select /* pgwatch_generated */
15771667 current_database() as tag_datname,
15781668 schemaname as tag_schemaname,
@@ -1592,10 +1682,31 @@ metrics:
15921682 extract(epoch from greatest(last_autoanalyze, last_analyze, '1970-01-01Z'))::int8 as last_analyze,
15931683 (vacuum_count + autovacuum_count) as vacuum_count,
15941684 (analyze_count + autoanalyze_count) as analyze_count
1595- from
1596- pg_stat_all_tables
1597- order by n_live_tup + n_dead_tup desc
1598- limit 5000
1685+ from ranked
1686+ where rownum <= 100
1687+ union all
1688+ select
1689+ current_database() as tag_datname,
1690+ '$other$'::text as tag_schemaname,
1691+ '$other$'::text as tag_relname,
1692+ coalesce(sum(seq_scan), 0)::int8 as seq_scan,
1693+ coalesce(sum(seq_tup_read), 0)::int8 as seq_tup_read,
1694+ coalesce(sum(idx_scan), 0)::int8 as idx_scan,
1695+ coalesce(sum(idx_tup_fetch), 0)::int8 as idx_tup_fetch,
1696+ coalesce(sum(n_tup_ins), 0)::int8 as n_tup_ins,
1697+ coalesce(sum(n_tup_upd), 0)::int8 as n_tup_upd,
1698+ coalesce(sum(n_tup_del), 0)::int8 as n_tup_del,
1699+ coalesce(sum(n_tup_hot_upd), 0)::int8 as n_tup_hot_upd,
1700+ coalesce(sum(n_live_tup), 0)::int8 as n_live_tup,
1701+ coalesce(sum(n_dead_tup), 0)::int8 as n_dead_tup,
1702+ extract(epoch from greatest(max(last_autovacuum), max(last_vacuum), '1970-01-01Z'))::int8 as last_vacuum,
1703+ extract(epoch from greatest(max(last_autoanalyze), max(last_analyze), '1970-01-01Z'))::int8 as last_analyze,
1704+ coalesce(sum(vacuum_count + autovacuum_count), 0)::int8 as vacuum_count,
1705+ coalesce(sum(analyze_count + autoanalyze_count), 0)::int8 as analyze_count
1706+ from ranked
1707+ where rownum > 100
1708+ group by ()
1709+ having count(*) > 0
15991710 gauges :
16001711 - seq_scan
16011712 - seq_tup_read
@@ -2881,60 +2992,133 @@ metrics:
28812992 statement_timeout_seconds : 15
28822993 pg_statio_all_tables :
28832994 description : >
2884- Retrieves table-level I/O statistics from the PostgreSQL `pg_statio_all_tables` view, providing insights into I/O operations for all tables.
2885- It returns block-level read and hit statistics for heap, index, TOAST, and TOAST index operations broken down by schema and table.
2886- Joined with pg_class for efficient ordering by table size.
2887- This metric helps administrators monitor table-level I/O performance and identify which tables are generating the most I/O activity.
2888- Compatible with all PostgreSQL versions.
2995+ Retrieves table-level I/O statistics from `pg_statio_all_tables`, returning
2996+ block-level read and hit counters for heap, index, TOAST and TOAST-index
2997+ pages. Adapts the top-N + `'$other$'` bucket pattern from pgwatch2
2998+ PostgresAI edition (gitlab.com/postgres-ai/pgwatch2): ranks tables by
2999+ heap_blks_read, keeps the top 100, and folds the tail into a single
3000+ `'$other$'` row so totals remain accurate while cardinality stays bounded.
3001+ Reads pg_statio_all_tables (not pg_statio_user_tables) so I/O on pg_catalog,
3002+ pg_toast and _timescaledb_internal stays visible — those tables enter the
3003+ top-N by activity, not by schema membership. The zero-counter row skip is
3004+ kept (those rows literally carry no information and are not identity-based).
3005+ The `'$other$'` sentinel starts with `$`, which is not legal as the first
3006+ character of an unquoted Postgres identifier, so it cannot collide with
3007+ any real schema or table name. Compatible with all Postgres versions.
28893008 sqls :
28903009 11 : |-
3010+ with ranked as (
3011+ select
3012+ row_number() over (
3013+ order by heap_blks_read desc nulls last,
3014+ schemaname, relname
3015+ ) as rownum,
3016+ schemaname,
3017+ relname,
3018+ heap_blks_read,
3019+ heap_blks_hit,
3020+ idx_blks_read,
3021+ idx_blks_hit,
3022+ toast_blks_read,
3023+ toast_blks_hit,
3024+ tidx_blks_read,
3025+ tidx_blks_hit
3026+ from pg_statio_all_tables
3027+ where
3028+ heap_blks_read > 0 or heap_blks_hit > 0
3029+ or idx_blks_read > 0 or idx_blks_hit > 0
3030+ or toast_blks_read > 0 or toast_blks_hit > 0
3031+ or tidx_blks_read > 0 or tidx_blks_hit > 0
3032+ )
28913033 select /* pgwatch_generated */
28923034 (extract(epoch from now()) * 1e9)::int8 as epoch_ns,
28933035 current_database() as tag_datname,
2894- s.schemaname as tag_schemaname,
2895- s.relname as tag_relname,
2896- s.heap_blks_read,
2897- s.heap_blks_hit,
2898- s.idx_blks_read,
2899- s.idx_blks_hit,
2900- s.toast_blks_read,
2901- s.toast_blks_hit,
2902- s.tidx_blks_read,
2903- s.tidx_blks_hit
2904- from
2905- pg_statio_all_tables as s
2906- join pg_class as c on
2907- s.relname = c.relname
2908- and s.schemaname = c.relnamespace::regnamespace::name
2909- order by c.relpages desc
2910- limit 5000;
3036+ schemaname as tag_schemaname,
3037+ relname as tag_relname,
3038+ heap_blks_read,
3039+ heap_blks_hit,
3040+ idx_blks_read,
3041+ idx_blks_hit,
3042+ toast_blks_read,
3043+ toast_blks_hit,
3044+ tidx_blks_read,
3045+ tidx_blks_hit
3046+ from ranked
3047+ where rownum <= 100
3048+ union all
3049+ select
3050+ (extract(epoch from now()) * 1e9)::int8 as epoch_ns,
3051+ current_database() as tag_datname,
3052+ '$other$'::text as tag_schemaname,
3053+ '$other$'::text as tag_relname,
3054+ coalesce(sum(heap_blks_read), 0)::int8 as heap_blks_read,
3055+ coalesce(sum(heap_blks_hit), 0)::int8 as heap_blks_hit,
3056+ coalesce(sum(idx_blks_read), 0)::int8 as idx_blks_read,
3057+ coalesce(sum(idx_blks_hit), 0)::int8 as idx_blks_hit,
3058+ coalesce(sum(toast_blks_read), 0)::int8 as toast_blks_read,
3059+ coalesce(sum(toast_blks_hit), 0)::int8 as toast_blks_hit,
3060+ coalesce(sum(tidx_blks_read), 0)::int8 as tidx_blks_read,
3061+ coalesce(sum(tidx_blks_hit), 0)::int8 as tidx_blks_hit
3062+ from ranked
3063+ where rownum > 100
3064+ group by ()
3065+ having count(*) > 0;
29113066 gauges :
29123067 - ' *'
29133068 statement_timeout_seconds : 15
29143069 pg_statio_all_indexes :
29153070 description : >
2916- Retrieves index-level I/O statistics from the PostgreSQL `pg_statio_all_indexes` view, providing insights into I/O operations for all indexes.
2917- It returns block-level read and hit statistics for index operations broken down by schema, table, and index name.
2918- Joined with pg_class for efficient ordering by index size.
2919- This metric helps administrators monitor index-level I/O performance and identify which indexes are generating the most I/O activity.
2920- Compatible with all PostgreSQL versions.
3071+ Retrieves index-level I/O statistics from `pg_statio_all_indexes`, returning
3072+ block-level read and hit counters per index. Adapts the top-N + `'$other$'`
3073+ bucket pattern from pgwatch2 PostgresAI edition
3074+ (gitlab.com/postgres-ai/pgwatch2): ranks indexes by idx_blks_read, keeps the
3075+ top 100, folds the tail into a single `'$other$'` row, and drops indexes
3076+ with no I/O activity (zero-counter rows carry no information).
3077+ Reads pg_statio_all_indexes (not pg_statio_user_indexes) so catalog,
3078+ pg_toast and _timescaledb_internal indexes stay visible: a hot catalog
3079+ index will rank into the top-N by activity, not be hidden by schema name.
3080+ The `'$other$'` sentinel starts with `$`, which is not legal as the first
3081+ character of an unquoted Postgres identifier, so it cannot collide with
3082+ any real schema, table or index name. Compatible with all Postgres versions.
29213083 sqls :
29223084 11 : |-
3085+ with ranked as (
3086+ select
3087+ row_number() over (
3088+ order by idx_blks_read desc nulls last,
3089+ schemaname, relname, indexrelname
3090+ ) as rownum,
3091+ schemaname,
3092+ relname,
3093+ indexrelname,
3094+ idx_blks_read,
3095+ idx_blks_hit
3096+ from pg_statio_all_indexes
3097+ where idx_blks_read > 0 or idx_blks_hit > 0
3098+ )
29233099 select /* pgwatch_generated */
29243100 (extract(epoch from now()) * 1e9)::int8 as epoch_ns,
29253101 current_database() as tag_datname,
2926- s.schemaname as tag_schemaname,
2927- s.relname as tag_relname,
2928- s.indexrelname as tag_indexrelname,
2929- s.idx_blks_read,
2930- s.idx_blks_hit
2931- from
2932- pg_statio_all_indexes as s
2933- join pg_class as c on
2934- s.indexrelname = c.relname
2935- and s.schemaname = c.relnamespace::regnamespace::name
2936- order by c.relpages desc
2937- limit 5000;
3102+ schemaname as tag_schemaname,
3103+ relname as tag_relname,
3104+ indexrelname as tag_indexrelname,
3105+ idx_blks_read,
3106+ idx_blks_hit
3107+ from ranked
3108+ where rownum <= 100
3109+ union all
3110+ select
3111+ (extract(epoch from now()) * 1e9)::int8 as epoch_ns,
3112+ current_database() as tag_datname,
3113+ '$other$'::text as tag_schemaname,
3114+ '$other$'::text as tag_relname,
3115+ '$other$'::text as tag_indexrelname,
3116+ coalesce(sum(idx_blks_read), 0)::int8 as idx_blks_read,
3117+ coalesce(sum(idx_blks_hit), 0)::int8 as idx_blks_hit
3118+ from ranked
3119+ where rownum > 100
3120+ group by ()
3121+ having count(*) > 0;
29383122 gauges :
29393123 - ' *'
29403124 statement_timeout_seconds : 15
0 commit comments