On Mon Jun 8, 2026 at 12:16 AM UTC, Michael Paquier wrote: > On Thu, Jun 04, 2026 at 04:59:00PM +0000, Tristan Partin wrote: >> I believe that I also captured this correctly in v2. > > No v2 of the patch has been attached. :D
I am not a very smart person! :facepalm: -- Tristan Partin PostgreSQL Contributors Team AWS (https://aws.amazon.com)
From 62c2dac137d72bb76cbd01a48019c89d01535565 Mon Sep 17 00:00:00 2001 From: Tristan Partin <[email protected]> Date: Thu, 30 Apr 2026 16:54:29 +0000 Subject: [PATCH v2] Add pg_stat_kind_info view and pg_stat_get_kind_info() Expose metadata about loaded statistics kinds through the pg_stat_kind_info view backed by the pg_stat_get_kind_info() function. Each row describes one statistics kind: id: numeric identifier of the kind name: name of the kind of stats count: number of entries counted builtin: whether this is a builtin kind fixed_amount: whether this kind tracks a fixed amount of data accessed_across_databases: whether this kind can be accessed across databases written_to_file: whether this kind is written out to the statistics file shared_size: the size in bytes of a shared memory entry for this kind With the view, extension authors can easily confirm that their custom statistics kinds are loaded. We can also figure out how shared memory is being used by statistics. Signed-off-by: Tristan Partin <[email protected]> --- doc/src/sgml/monitoring.sgml | 141 ++++++++++++++++++ src/backend/catalog/system_views.sql | 12 ++ src/backend/utils/adt/pgstatfuncs.c | 59 ++++++++ src/include/catalog/pg_proc.dat | 8 + .../test_custom_stats/t/001_custom_stats.pl | 13 +- src/test/regress/expected/rules.out | 9 ++ src/test/regress/expected/stats.out | 22 +++ src/test/regress/expected/sysviews.out | 7 + src/test/regress/sql/stats.sql | 8 + src/test/regress/sql/sysviews.sql | 3 + 10 files changed, 281 insertions(+), 1 deletion(-) diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 08d5b82455..a3f33de96d 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -509,6 +509,15 @@ postgres 27093 0.0 0.0 30096 2752 ? Ss 11:34 0:00 postgres: ser </entry> </row> + <row> + <entry><structname>pg_stat_kind_info</structname><indexterm><primary>pg_stat_kind_info</primary></indexterm></entry> + <entry> + One row for each loaded statistics kind, showing metadata about the kind. + See <link linkend="monitoring-pg-stat-kind-info-view"> + <structname>pg_stat_kind_info</structname></link> for details. + </entry> + </row> + <row> <entry><structname>pg_stat_lock</structname><indexterm><primary>pg_stat_lock</primary></indexterm></entry> <entry> @@ -3303,6 +3312,138 @@ description | Waiting for a newly initialized WAL file to reach durable storage </sect2> + <sect2 id="monitoring-pg-stat-kind-info-view"> + <title><structname>pg_stat_kind_info</structname></title> + + <indexterm> + <primary>pg_stat_kind_info</primary> + </indexterm> + + <para> + The <structname>pg_stat_kind_info</structname> view contains one row for each + loaded statistics kind, including both built-in and custom kinds. + </para> + + <table id="pg-stat-kind-info-view" xreflabel="pg_stat_kind_info"> + <title><structname>pg_stat_kind_info</structname> View</title> + <tgroup cols="1"> + <thead> + <row> + <entry role="catalog_table_entry"> + <para role="column_definition"> + Column Type + </para> + <para> + Description + </para> + </entry> + </row> + </thead> + <tbody> + <row> + <entry role="catalog_table_entry"> + <para role="column_definition"> + <structfield>id</structfield> <type>integer</type> + </para> + <para> + Numeric identifier of the statistics kind. + </para> + </entry> + </row> + + <row> + <entry role="catalog_table_entry"> + <para role="column_definition"> + <structfield>name</structfield> <type>text</type> + </para> + <para> + Name of the statistics kind. + </para> + </entry> + </row> + + <row> + <entry role="catalog_table_entry"> + <para role="column_definition"> + <structfield>count</structfield> <type>bigint</type> + </para> + <para> + Number of tracked entries for this kind. For fixed-amount kinds, this is + always 1. For variable-numbered kinds, this is the number of objects + currently tracked. <literal>NULL</literal> if the kind does not track + entry counts. + </para> + </entry> + </row> + + <row> + <entry role="catalog_table_entry"> + <para role="column_definition"> + <structfield>builtin</structfield> <type>boolean</type> + </para> + <para> + True if this is a built-in statistics kind, false if it was registered + by an extension. + </para> + </entry> + </row> + + <row> + <entry role="catalog_table_entry"> + <para role="column_definition"> + <structfield>fixed_amount</structfield> <type>boolean</type> + </para> + <para> + True if this kind tracks a fixed amount of data (a single, statically + allocated entry), false if it tracks a variable number of entries + keyed by object identifier. + </para> + </entry> + </row> + + <row> + <entry role="catalog_table_entry"> + <para role="column_definition"> + <structfield>accessed_across_databases</structfield> <type>boolean</type> + </para> + <para> + True if entries of this kind are accessed across databases (cluster-wide + statistics), false if they are scoped to a single database. + </para> + </entry> + </row> + + <row> + <entry role="catalog_table_entry"> + <para role="column_definition"> + <structfield>written_to_file</structfield> <type>boolean</type> + </para> + <para> + True if entries of this kind are persisted to the statistics file at + shutdown and reloaded on startup, false if they are kept only in + shared memory. + </para> + </entry> + </row> + + <row> + <entry role="catalog_table_entry"> + <para role="column_definition"> + <structfield>shared_size</structfield> <type>bigint</type> + </para> + <para> + Size in bytes of a shared memory entry for this statistics kind. + <literal>NULL</literal> for built-in fixed-amount kinds, whose + single entry lives in a statically-allocated slot rather than a + sized shared memory entry. + </para> + </entry> + </row> + </tbody> + </tgroup> + </table> + </sect2> + <sect2 id="monitoring-pg-stat-lock-view"> <title><structname>pg_stat_lock</structname></title> diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 73a1c1c467..5ce455f7ec 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -1282,6 +1282,18 @@ SELECT b.stats_reset FROM pg_stat_get_io() b; +CREATE VIEW pg_stat_kind_info AS + SELECT + k.id, + k.name, + k.count, + k.builtin, + k.fixed_amount, + k.accessed_across_databases, + k.written_to_file, + k.shared_size + FROM pg_stat_get_kind_info() k; + CREATE VIEW pg_stat_wal AS SELECT w.wal_records, diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 6f9c9c72de..acb8c4883e 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -30,6 +30,8 @@ #include "storage/procarray.h" #include "utils/acl.h" #include "utils/builtins.h" +#include "utils/pgstat_internal.h" +#include "utils/pgstat_kind.h" #include "utils/timestamp.h" #include "utils/tuplestore.h" #include "utils/wait_event.h" @@ -1638,6 +1640,63 @@ pg_stat_get_backend_io(PG_FUNCTION_ARGS) return (Datum) 0; } +Datum +pg_stat_get_kind_info(PG_FUNCTION_ARGS) +{ +#define PG_STAT_KIND_INFO_COLS 8 + ReturnSetInfo *rsinfo; + + InitMaterializedSRF(fcinfo, 0); + rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + + for (int kind = PGSTAT_KIND_MIN; kind <= PGSTAT_KIND_MAX; kind++) + { + Datum values[PG_STAT_KIND_INFO_COLS] = {0}; + bool nulls[PG_STAT_KIND_INFO_COLS] = {0}; + const PgStat_KindInfo *info; + + info = pgstat_get_kind_info(kind); + if (info == NULL) + continue; + + values[0] = Int32GetDatum(kind); + values[1] = CStringGetTextDatum(info->name); + + /* For fixed-amount kinds, count is always 1. The entry is stored in + * PgStat_ShmemControl. If it is not a fixed-amount, then report the + * count of entries if tracked, or NULL if not tracked. + */ + if (info->fixed_amount) + values[2] = Int64GetDatum(1); + else if (info->track_entry_count) + values[2] = Int64GetDatum(pgstat_get_entry_count(kind)); + else + nulls[2] = true; + + values[3] = BoolGetDatum(pgstat_is_kind_builtin(kind)); + values[4] = BoolGetDatum(info->fixed_amount); + values[5] = BoolGetDatum(info->accessed_across_databases); + values[6] = BoolGetDatum(info->write_to_file); + + /* + * Built-in fixed-amount kinds store their single entry in + * PgStat_ShmemControl, so shared_size is unused and left zero in their + * PgStat_KindInfo. Reporting it would be misleading, so report NULL + * instead. Custom fixed-amount kinds declare shared_size to size their + * slot in custom_data[], so it is meaningful and reported as-is. + */ + if (info->fixed_amount && pgstat_is_kind_builtin(kind)) + nulls[7] = true; + else + values[7] = Int64GetDatum(info->shared_size); + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); + } + + return (Datum) 0; +#undef PG_STAT_KIND_INFO_COLS +} + /* * pg_stat_wal_build_tuple * diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index be157a5fbe..a9ac9304b9 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -6078,6 +6078,14 @@ proargnames => '{backend_pid,backend_type,object,context,reads,read_bytes,read_time,writes,write_bytes,write_time,writebacks,writeback_time,extends,extend_bytes,extend_time,hits,evictions,reuses,fsyncs,fsync_time,stats_reset}', prosrc => 'pg_stat_get_backend_io' }, +{ oid => '8683', descr => 'statistics: information about loaded statistics kinds', + proname => 'pg_stat_get_kind_info', prorows => '20', proretset => 't', + provolatile => 'v', proparallel => 'r', prorettype => 'record', + proargtypes => '', proallargtypes => '{int4,text,int8,bool,bool,bool,bool,int8}', + proargmodes => '{o,o,o,o,o,o,o,o}', + proargnames => '{id,name,count,builtin,fixed_amount,accessed_across_databases,written_to_file,shared_size}', + prosrc => 'pg_stat_get_kind_info' }, + { oid => '1136', descr => 'statistics: information about WAL activity', proname => 'pg_stat_get_wal', proisstrict => 'f', provolatile => 's', proparallel => 'r', prorettype => 'record', proargtypes => '', diff --git a/src/test/modules/test_custom_stats/t/001_custom_stats.pl b/src/test/modules/test_custom_stats/t/001_custom_stats.pl index 9e6a7a3857..1b11d81687 100644 --- a/src/test/modules/test_custom_stats/t/001_custom_stats.pl +++ b/src/test/modules/test_custom_stats/t/001_custom_stats.pl @@ -27,6 +27,17 @@ $node->safe_psql('postgres', q(CREATE EXTENSION test_custom_var_stats)); $node->safe_psql('postgres', q(CREATE EXTENSION test_custom_fixed_stats)); +# Verify custom stats kinds appear in pg_stat_kind_info. +my $result = $node->safe_psql('postgres', + q(SELECT id, name, builtin, fixed_amount, accessed_across_databases, + written_to_file, shared_size > 0 + FROM pg_stat_kind_info + WHERE name LIKE 'test_custom%' ORDER BY id)); +is($result, + "25|test_custom_var_stats|f|f|t|t|t\n" . + "26|test_custom_fixed_stats|f|t|f|t|t", + "custom stats kinds visible in pg_stat_kind_info"); + # Create entries for variable-sized stats. $node->safe_psql('postgres', q(select test_custom_stats_var_create('entry1', 'Test entry 1'))); @@ -63,7 +74,7 @@ $node->safe_psql('postgres', q(select test_custom_stats_fixed_update())); # Test data reports. -my $result = $node->safe_psql('postgres', +$result = $node->safe_psql('postgres', q(select * from test_custom_stats_var_report('entry1'))); is( $result, "entry1|2|Test entry 1", diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index a65a5bf0c4..4ee3191f9d 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1967,6 +1967,15 @@ pg_stat_io| SELECT backend_type, fsync_time, stats_reset FROM pg_stat_get_io() b(backend_type, object, context, reads, read_bytes, read_time, writes, write_bytes, write_time, writebacks, writeback_time, extends, extend_bytes, extend_time, hits, evictions, reuses, fsyncs, fsync_time, stats_reset); +pg_stat_kind_info| SELECT id, + name, + count, + builtin, + fixed_amount, + accessed_across_databases, + written_to_file, + shared_size + FROM pg_stat_get_kind_info() k(id, name, count, builtin, fixed_amount, accessed_across_databases, written_to_file, shared_size); pg_stat_lock| SELECT locktype, waits, wait_time, diff --git a/src/test/regress/expected/stats.out b/src/test/regress/expected/stats.out index bbb1db3c43..33197a62a5 100644 --- a/src/test/regress/expected/stats.out +++ b/src/test/regress/expected/stats.out @@ -113,6 +113,28 @@ walwriter|wal|init walwriter|wal|normal (95 rows) \a +-- List of loaded statistics kinds. +\a +SELECT name, id, fixed_amount, accessed_across_databases, written_to_file + FROM pg_stat_kind_info + WHERE builtin + ORDER BY name COLLATE "C"; +name|id|fixed_amount|accessed_across_databases|written_to_file +archiver|7|t|f|t +backend|6|f|t|f +bgwriter|8|t|f|t +checkpointer|9|t|f|t +database|1|f|t|t +function|3|f|f|t +io|10|t|f|t +lock|11|t|f|t +relation|2|f|f|t +replslot|4|f|t|t +slru|12|t|f|t +subscription|5|f|t|t +wal|13|t|f|t +(13 rows) +\a -- ensure that both seqscan and indexscan plans are allowed SET enable_seqscan TO on; SET enable_indexscan TO on; diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out index 132b56a586..ac9e033c2a 100644 --- a/src/test/regress/expected/sysviews.out +++ b/src/test/regress/expected/sysviews.out @@ -129,6 +129,13 @@ select count(*) > 0 as ok from pg_stat_slru; t (1 row) +-- There should be at least one statistics kind loaded +select count(*) > 0 as ok from pg_stat_kind_info; + ok +---- + t +(1 row) + -- There must be only one record select count(*) = 1 as ok from pg_stat_wal; ok diff --git a/src/test/regress/sql/stats.sql b/src/test/regress/sql/stats.sql index 610fd21fae..f79cbbd9d8 100644 --- a/src/test/regress/sql/stats.sql +++ b/src/test/regress/sql/stats.sql @@ -14,6 +14,14 @@ SELECT backend_type, object, context FROM pg_stat_io ORDER BY backend_type COLLATE "C", object COLLATE "C", context COLLATE "C"; \a +-- List of loaded statistics kinds. +\a +SELECT name, id, fixed_amount, accessed_across_databases, written_to_file + FROM pg_stat_kind_info + WHERE builtin + ORDER BY name COLLATE "C"; +\a + -- ensure that both seqscan and indexscan plans are allowed SET enable_seqscan TO on; SET enable_indexscan TO on; diff --git a/src/test/regress/sql/sysviews.sql b/src/test/regress/sql/sysviews.sql index 507e400ad4..193bf84dad 100644 --- a/src/test/regress/sql/sysviews.sql +++ b/src/test/regress/sql/sysviews.sql @@ -70,6 +70,9 @@ select count(*) >= 0 as ok from pg_prepared_xacts; -- There will surely be at least one SLRU cache select count(*) > 0 as ok from pg_stat_slru; +-- There should be at least one statistics kind loaded +select count(*) > 0 as ok from pg_stat_kind_info; + -- There must be only one record select count(*) = 1 as ok from pg_stat_wal; -- Tristan Partin https://tristan.partin.io
