Hi,

here is a bit improved version of the patch - I've been annoyed by how
the resetting works (per-entry timestamp, but resetting all entries) so
I've added a new function pg_stat_reset_slru() that allows resetting
either all entries or just one entry (identified by name). So

    SELECT pg_stat_reset_slru('clog');

resets just "clog" SLRU counters, while

    SELECT pg_stat_reset_slru(NULL);

resets all entries.

I've also done a bit of benchmarking, to see if this has measurable
impact (in which case it might deserve a new GUC), and I think it's not
measurable. I've used a tiny unlogged table (single row).

    CREATE UNLOGGED TABLE t (a int);
    INSERT INTO t VALUES (1);

and then short pgbench runs with a single client, updatint the row. I've
been unable to measure any regression, it's all well within 1% so noise.
But perhaps there's some other benchmark that I should do?


regards

--
Tomas Vondra                  http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
>From 1a065c21a9a791909cd1ca752db5aaf1f814fe37 Mon Sep 17 00:00:00 2001
From: Tomas Vondra <t...@fuzzy.cz>
Date: Thu, 26 Mar 2020 20:52:26 +0100
Subject: [PATCH] Collect SLRU statistics

Adds a new system view pg_stats_slru with stats about SLRU caches, and
a function pg_stat_reset_slru() to reset either all counters or just
counters for a single SLRU.

There is no SLRU registry this patch could use, so it simply uses the
SLRU name (which is also used for LWLock tranche name) as an identifier,
and a predefined list of SLRU names, and an extra "others" entry for
SLRUs without a dedicated entry. Presumably, the number of extensions
defining their own SLRU is very small.

Author: Tomas Vondra
Reviewed-by: Alvaro Herrera
Discussion: 
https://www.postgresql.org/message-id/flat/20200119143707.gyinppnigokesjok@development
---
 doc/src/sgml/monitoring.sgml         |  97 +++++++++
 src/backend/access/transam/slru.c    |  23 ++
 src/backend/catalog/system_views.sql |  14 ++
 src/backend/postmaster/pgstat.c      | 300 +++++++++++++++++++++++++++
 src/backend/utils/adt/pgstatfuncs.c  |  91 ++++++++
 src/include/catalog/pg_proc.dat      |  15 ++
 src/include/pgstat.h                 |  65 ++++++
 src/test/regress/expected/rules.out  |  10 +
 8 files changed, 615 insertions(+)

diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml
index 270178d57e..7ba0dbee6a 100644
--- a/doc/src/sgml/monitoring.sgml
+++ b/doc/src/sgml/monitoring.sgml
@@ -575,6 +575,13 @@ postgres   27093  0.0  0.0  30096  2752 ?        Ss   
11:34   0:00 postgres: ser
       yet included in <structname>pg_stat_user_functions</structname>).</entry>
      </row>
 
+     <row>
+      
<entry><structname>pg_stat_slru</structname><indexterm><primary>pg_stat_slru</primary></indexterm></entry>
+      <entry>One row per SLRU, showing statistics of operations. See
+       <xref linkend="pg-stat-slru-view"/> for details.
+      </entry>
+     </row>
+
     </tbody>
    </tgroup>
   </table>
@@ -3254,6 +3261,76 @@ SELECT pid, wait_event_type, wait_event FROM 
pg_stat_activity WHERE wait_event i
    </tgroup>
   </table>
 
+  <para>
+   The <structname>pg_stat_slru</structname> view will contain
+   one row for each tracked SLRU cache, showing statistics about access
+   to cached pages.
+  </para>
+
+  <table id="pg-stat-slru-view" xreflabel="pg_stat_slru">
+   <title><structname>pg_stat_slru</structname> View</title>
+   <tgroup cols="3">
+    <thead>
+    <row>
+      <entry>Column</entry>
+      <entry>Type</entry>
+      <entry>Description</entry>
+     </row>
+    </thead>
+
+   <tbody>
+    <row>
+     <entry><structfield>name</structfield></entry>
+     <entry><type>name</type></entry>
+     <entry>name of the SLRU</entry>
+    </row>
+    <row>
+     <entry><structfield>blks_zeroed</structfield></entry>
+     <entry><type>bigint</type></entry>
+     <entry>Number of blocks zeroed during initializations</entry>
+    </row>
+    <row>
+     <entry><structfield>blks_hit</structfield></entry>
+     <entry><type>biging</type></entry>
+     <entry>Number of times disk blocks were found already in the SLRU,
+      so that a read was not necessary (this only includes hits in the
+      SLRU, not the operating system's file system cache)
+     </entry>
+    </row>
+    <row>
+     <entry><structfield>blks_read</structfield></entry>
+     <entry><type>bigint</type></entry>
+     <entry>Number of disk blocks read for this SLRU</entry>
+    </row>
+    <row>
+     <entry><structfield>blks_written</structfield></entry>
+     <entry><type>bigint</type></entry>
+     <entry>Number of disk blocks written for this SLRU</entry>
+    </row>
+    <row>
+     <entry><structfield>blks_exists</structfield></entry>
+     <entry><type>bigint</type></entry>
+     <entry>Number of blocks checked for existence for this SLRU</entry>
+    </row>
+    <row>
+     <entry><structfield>flushes</structfield></entry>
+     <entry><type>bigint</type></entry>
+     <entry>Number of flushes of dirty data for this SLRU</entry>
+    </row>
+    <row>
+     <entry><structfield>truncates</structfield></entry>
+     <entry><type>bigint</type></entry>
+     <entry>Number of truncates for this SLRU</entry>
+    </row>
+    <row>
+     <entry><structfield>stats_reset</structfield></entry>
+     <entry><type>timestamp with time zone</type></entry>
+     <entry>Time at which these statistics were last reset</entry>
+    </row>
+   </tbody>
+   </tgroup>
+  </table>
+
   <para>
    The <structname>pg_stat_user_functions</structname> view will contain
    one row for each tracked function, showing statistics about executions of
@@ -3378,6 +3455,26 @@ SELECT pid, wait_event_type, wait_event FROM 
pg_stat_activity WHERE wait_event i
        function can be granted to others)
       </entry>
      </row>
+
+     <row>
+      
<entry><literal><function>pg_stat_reset_slru</function>(text)</literal><indexterm><primary>pg_stat_reset_slru</primary></indexterm></entry>
+      <entry><type>void</type></entry>
+      <entry>
+       Reset statistics either for a single SLRU or all SLRUs in the cluster
+       to zero (requires superuser privileges by default, but EXECUTE for this
+       function can be granted to others).
+       Calling <literal>pg_stat_reset_slru(NULL)</literal> will zero all the
+       counters shown in the <structname>pg_stat_slru</structname> view for
+       all SLRU caches.
+       Calling <literal>pg_stat_reset_slru(name)</literal> with names from a
+       predefined list (<literal>async</literal>, <literal>clog</literal>,
+       <literal>commit_timestamp</literal>, 
<literal>multixact_offset</literal>,
+       <literal>multixact_member</literal>, <literal>oldserxid</literal>,
+       <literal>pg_xact</literal>, <literal>subtrans</literal> and
+       <literal>other</literal>) resets counters for only that entry.
+       Names not included in this list are treated as <literal>other</literal>.
+      </entry>
+     </row>
     </tbody>
    </tgroup>
   </table>
diff --git a/src/backend/access/transam/slru.c 
b/src/backend/access/transam/slru.c
index d5b7a08f73..f7160dd574 100644
--- a/src/backend/access/transam/slru.c
+++ b/src/backend/access/transam/slru.c
@@ -286,6 +286,9 @@ SimpleLruZeroPage(SlruCtl ctl, int pageno)
        /* Assume this page is now the latest active page */
        shared->latest_page_number = pageno;
 
+       /* update the stats counter of zeroed pages */
+       pgstat_slru_count_page_zeroed(ctl);
+
        return slotno;
 }
 
@@ -403,6 +406,10 @@ SimpleLruReadPage(SlruCtl ctl, int pageno, bool write_ok,
                        }
                        /* Otherwise, it's ready to use */
                        SlruRecentlyUsed(shared, slotno);
+
+                       /* update the stats counter of pages found in the SLRU 
*/
+                       pgstat_slru_count_page_hit(ctl);
+
                        return slotno;
                }
 
@@ -444,6 +451,10 @@ SimpleLruReadPage(SlruCtl ctl, int pageno, bool write_ok,
                        SlruReportIOError(ctl, pageno, xid);
 
                SlruRecentlyUsed(shared, slotno);
+
+               /* update the stats counter of pages not found in SLRU */
+               pgstat_slru_count_page_read(ctl);
+
                return slotno;
        }
 }
@@ -596,6 +607,9 @@ SimpleLruDoesPhysicalPageExist(SlruCtl ctl, int pageno)
        bool            result;
        off_t           endpos;
 
+       /* update the stats counter of checked pages */
+       pgstat_slru_count_page_exists(ctl);
+
        SlruFileName(ctl, path, segno);
 
        fd = OpenTransientFile(path, O_RDONLY | PG_BINARY);
@@ -730,6 +744,9 @@ SlruPhysicalWritePage(SlruCtl ctl, int pageno, int slotno, 
SlruFlush fdata)
        char            path[MAXPGPATH];
        int                     fd = -1;
 
+       /* update the stats counter of written pages */
+       pgstat_slru_count_page_written(ctl);
+
        /*
         * Honor the write-WAL-before-data rule, if appropriate, so that we do 
not
         * write out data before associated WAL records.  This is the same 
action
@@ -1125,6 +1142,9 @@ SimpleLruFlush(SlruCtl ctl, bool allow_redirtied)
        int                     i;
        bool            ok;
 
+       /* update the stats counter of flushes */
+       pgstat_slru_count_flush(ctl);
+
        /*
         * Find and write dirty pages
         */
@@ -1186,6 +1206,9 @@ SimpleLruTruncate(SlruCtl ctl, int cutoffPage)
        SlruShared      shared = ctl->shared;
        int                     slotno;
 
+       /* update the stats counter of truncates */
+       pgstat_slru_count_truncate(ctl);
+
        /*
         * The cutoff point is the start of the segment containing cutoffPage.
         */
diff --git a/src/backend/catalog/system_views.sql 
b/src/backend/catalog/system_views.sql
index 5a6dc61630..09e226f34d 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -792,6 +792,19 @@ CREATE VIEW pg_stat_replication AS
         JOIN pg_stat_get_wal_senders() AS W ON (S.pid = W.pid)
         LEFT JOIN pg_authid AS U ON (S.usesysid = U.oid);
 
+CREATE VIEW pg_stat_slru AS
+    SELECT
+            s.name,
+            s.blks_zeroed,
+            s.blks_hit,
+            s.blks_read,
+            s.blks_written,
+            s.blks_exists,
+            s.flushes,
+            s.truncates,
+            s.stats_reset
+    FROM pg_stat_get_slru() s;
+
 CREATE VIEW pg_stat_wal_receiver AS
     SELECT
             s.pid,
@@ -1409,6 +1422,7 @@ REVOKE EXECUTE ON FUNCTION pg_promote(boolean, integer) 
FROM public;
 
 REVOKE EXECUTE ON FUNCTION pg_stat_reset() FROM public;
 REVOKE EXECUTE ON FUNCTION pg_stat_reset_shared(text) FROM public;
+REVOKE EXECUTE ON FUNCTION pg_stat_reset_slru(text) FROM public;
 REVOKE EXECUTE ON FUNCTION pg_stat_reset_single_table_counters(oid) FROM 
public;
 REVOKE EXECUTE ON FUNCTION pg_stat_reset_single_function_counters(oid) FROM 
public;
 
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 4763c24be9..ea0fdade58 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -141,6 +141,24 @@ char          *pgstat_stat_tmpname = NULL;
  */
 PgStat_MsgBgWriter BgWriterStats;
 
+/*
+ * SLRU statistics counters (unused in other processes) stored directly in
+ * stats structure so it can be sent without needing to copy things around.
+ * We assume this inits to zeroes.
+ *
+ * There's a separte entry for each SLRU we have. The "other" entry is used
+ * for all SLRUs without an explicit entry (e.g. SLRUs in extensions).
+ */
+static char *slru_names[] = {"async", "clog", "commit_timestamp",
+                                                         "multixact_offset", 
"multixact_member",
+                                                         "oldserxid", 
"pg_xact", "subtrans",
+                                                         "other" /* has to be 
last */};
+
+#define SLRU_NUM_ELEMENTS      (sizeof(slru_names) / sizeof(char *))
+
+/* entries in the same order as slru_names */
+PgStat_MsgSLRU SLRUStats[SLRU_NUM_ELEMENTS];
+
 /* ----------
  * Local data
  * ----------
@@ -255,6 +273,7 @@ static int  localNumBackends = 0;
  */
 static PgStat_ArchiverStats archiverStats;
 static PgStat_GlobalStats globalStats;
+static PgStat_SLRUStats slruStats[SLRU_NUM_ELEMENTS];
 
 /*
  * List of OIDs of databases we need to write out.  If an entry is InvalidOid,
@@ -297,6 +316,7 @@ static bool pgstat_db_requested(Oid databaseid);
 
 static void pgstat_send_tabstat(PgStat_MsgTabstat *tsmsg);
 static void pgstat_send_funcstats(void);
+static void pgstat_send_slru(void);
 static HTAB *pgstat_collect_oids(Oid catalogid, AttrNumber anum_oid);
 
 static PgStat_TableStatus *get_tabstat_entry(Oid rel_id, bool isshared);
@@ -319,11 +339,13 @@ static void pgstat_recv_dropdb(PgStat_MsgDropdb *msg, int 
len);
 static void pgstat_recv_resetcounter(PgStat_MsgResetcounter *msg, int len);
 static void pgstat_recv_resetsharedcounter(PgStat_MsgResetsharedcounter *msg, 
int len);
 static void pgstat_recv_resetsinglecounter(PgStat_MsgResetsinglecounter *msg, 
int len);
+static void pgstat_recv_resetslrucounter(PgStat_MsgResetslrucounter *msg, int 
len);
 static void pgstat_recv_autovac(PgStat_MsgAutovacStart *msg, int len);
 static void pgstat_recv_vacuum(PgStat_MsgVacuum *msg, int len);
 static void pgstat_recv_analyze(PgStat_MsgAnalyze *msg, int len);
 static void pgstat_recv_archiver(PgStat_MsgArchiver *msg, int len);
 static void pgstat_recv_bgwriter(PgStat_MsgBgWriter *msg, int len);
+static void pgstat_recv_slru(PgStat_MsgSLRU *msg, int len);
 static void pgstat_recv_funcstat(PgStat_MsgFuncstat *msg, int len);
 static void pgstat_recv_funcpurge(PgStat_MsgFuncpurge *msg, int len);
 static void pgstat_recv_recoveryconflict(PgStat_MsgRecoveryConflict *msg, int 
len);
@@ -907,6 +929,9 @@ pgstat_report_stat(bool force)
 
        /* Now, send function statistics */
        pgstat_send_funcstats();
+
+       /* Finally send SLRU statistics */
+       pgstat_send_slru();
 }
 
 /*
@@ -1372,6 +1397,30 @@ pgstat_reset_single_counter(Oid objoid, 
PgStat_Single_Reset_Type type)
        pgstat_send(&msg, sizeof(msg));
 }
 
+/* ----------
+ * pgstat_reset_slru_counter() -
+ *
+ *     Tell the statistics collector to reset a single SLRU counter, or all
+ *     SLRU counters (when name is null).
+ *
+ *     Permission checking for this function is managed through the normal
+ *     GRANT system.
+ * ----------
+ */
+void
+pgstat_reset_slru_counter(const char *name)
+{
+       PgStat_MsgResetslrucounter msg;
+
+       if (pgStatSock == PGINVALID_SOCKET)
+               return;
+
+       pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_RESETSLRUCOUNTER);
+       msg.m_index = (name) ? pgstat_slru_index(name) : -1;
+
+       pgstat_send(&msg, sizeof(msg));
+}
+
 /* ----------
  * pgstat_report_autovac() -
  *
@@ -2622,6 +2671,23 @@ pgstat_fetch_global(void)
 }
 
 
+/*
+ * ---------
+ * pgstat_fetch_slru() -
+ *
+ *     Support function for the SQL-callable pgstat* functions. Returns
+ *     a pointer to the slru statistics struct.
+ * ---------
+ */
+PgStat_SLRUStats *
+pgstat_fetch_slru(void)
+{
+       backend_read_statsfile();
+
+       return slruStats;
+}
+
+
 /* ------------------------------------------------------------
  * Functions for management of the shared-memory PgBackendStatus array
  * ------------------------------------------------------------
@@ -4325,6 +4391,46 @@ pgstat_send_bgwriter(void)
        MemSet(&BgWriterStats, 0, sizeof(BgWriterStats));
 }
 
+/* ----------
+ * pgstat_send_slru() -
+ *
+ *             Send SLRU statistics to the collector
+ * ----------
+ */
+static void
+pgstat_send_slru(void)
+{
+       int             i;
+
+       /* We assume this initializes to zeroes */
+       static const PgStat_MsgSLRU all_zeroes;
+
+       for (i = 0; i < SLRU_NUM_ELEMENTS; i++)
+       {
+               /*
+                * This function can be called even if nothing at all has 
happened. In
+                * this case, avoid sending a completely empty message to the 
stats
+                * collector.
+                */
+               if (memcmp(&SLRUStats[i], &all_zeroes, sizeof(PgStat_MsgSLRU)) 
== 0)
+                       continue;
+
+               /* set the SLRU type before each send */
+               SLRUStats[i].m_index = i;
+
+               /*
+                * Prepare and send the message
+                */
+               pgstat_setheader(&SLRUStats[i].m_hdr, PGSTAT_MTYPE_SLRU);
+               pgstat_send(&SLRUStats[i], sizeof(PgStat_MsgSLRU));
+
+               /*
+                * Clear out the statistics buffer, so it can be re-used.
+                */
+               MemSet(&SLRUStats[i], 0, sizeof(PgStat_MsgSLRU));
+       }
+}
+
 
 /* ----------
  * PgstatCollectorMain() -
@@ -4493,6 +4599,11 @@ PgstatCollectorMain(int argc, char *argv[])
                                                                                
                   len);
                                        break;
 
+                               case PGSTAT_MTYPE_RESETSLRUCOUNTER:
+                                       
pgstat_recv_resetslrucounter(&msg.msg_resetslrucounter,
+                                                                               
                 len);
+                                       break;
+
                                case PGSTAT_MTYPE_AUTOVAC_START:
                                        
pgstat_recv_autovac(&msg.msg_autovacuum_start, len);
                                        break;
@@ -4513,6 +4624,10 @@ PgstatCollectorMain(int argc, char *argv[])
                                        pgstat_recv_bgwriter(&msg.msg_bgwriter, 
len);
                                        break;
 
+                               case PGSTAT_MTYPE_SLRU:
+                                       pgstat_recv_slru(&msg.msg_slru, len);
+                                       break;
+
                                case PGSTAT_MTYPE_FUNCSTAT:
                                        pgstat_recv_funcstat(&msg.msg_funcstat, 
len);
                                        break;
@@ -4781,6 +4896,12 @@ pgstat_write_statsfiles(bool permanent, bool allDbs)
        rc = fwrite(&archiverStats, sizeof(archiverStats), 1, fpout);
        (void) rc;                                      /* we'll check for 
error with ferror */
 
+       /*
+        * Write SLRU stats struct
+        */
+       rc = fwrite(slruStats, sizeof(slruStats), 1, fpout);
+       (void) rc;                                      /* we'll check for 
error with ferror */
+
        /*
         * Walk through the database table.
         */
@@ -5016,6 +5137,7 @@ pgstat_read_statsfiles(Oid onlydb, bool permanent, bool 
deep)
        int32           format_id;
        bool            found;
        const char *statfile = permanent ? PGSTAT_STAT_PERMANENT_FILENAME : 
pgstat_stat_filename;
+       int                     i;
 
        /*
         * The tables will live in pgStatLocalContext.
@@ -5038,6 +5160,7 @@ pgstat_read_statsfiles(Oid onlydb, bool permanent, bool 
deep)
         */
        memset(&globalStats, 0, sizeof(globalStats));
        memset(&archiverStats, 0, sizeof(archiverStats));
+       memset(&slruStats, 0, sizeof(slruStats));
 
        /*
         * Set the current timestamp (will be kept only in case we can't load an
@@ -5046,6 +5169,13 @@ pgstat_read_statsfiles(Oid onlydb, bool permanent, bool 
deep)
        globalStats.stat_reset_timestamp = GetCurrentTimestamp();
        archiverStats.stat_reset_timestamp = globalStats.stat_reset_timestamp;
 
+       /*
+        * Set the same reset timestamp for all SLRU items (one
+        * day we might allow resetting individual SLRUs).
+        */
+       for (i = 0; i < SLRU_NUM_ELEMENTS; i++)
+               slruStats[i].stat_reset_timestamp = 
globalStats.stat_reset_timestamp;
+
        /*
         * Try to open the stats file. If it doesn't exist, the backends simply
         * return zero for anything and the collector simply starts from scratch
@@ -5108,6 +5238,17 @@ pgstat_read_statsfiles(Oid onlydb, bool permanent, bool 
deep)
                goto done;
        }
 
+       /*
+        * Read SLRU stats struct
+        */
+       if (fread(slruStats, 1, sizeof(slruStats), fpin) != sizeof(slruStats))
+       {
+               ereport(pgStatRunningInCollector ? LOG : WARNING,
+                               (errmsg("corrupted statistics file \"%s\"", 
statfile)));
+               memset(&slruStats, 0, sizeof(slruStats));
+               goto done;
+       }
+
        /*
         * We found an existing collector stats file. Read it and put all the
         * hashtable entries into place.
@@ -5406,9 +5547,11 @@ pgstat_read_db_statsfile_timestamp(Oid databaseid, bool 
permanent,
        PgStat_StatDBEntry dbentry;
        PgStat_GlobalStats myGlobalStats;
        PgStat_ArchiverStats myArchiverStats;
+       PgStat_SLRUStats mySLRUStats;
        FILE       *fpin;
        int32           format_id;
        const char *statfile = permanent ? PGSTAT_STAT_PERMANENT_FILENAME : 
pgstat_stat_filename;
+       int                     i;
 
        /*
         * Try to open the stats file.  As above, anything but ENOENT is worthy 
of
@@ -5460,6 +5603,21 @@ pgstat_read_db_statsfile_timestamp(Oid databaseid, bool 
permanent,
                return false;
        }
 
+       /*
+        * Read SLRU stats struct
+        */
+       for (i = 0; i < SLRU_NUM_ELEMENTS; i++)
+       {
+               if (fread(&mySLRUStats, 1, sizeof(PgStat_SLRUStats),
+                                 fpin) != sizeof(PgStat_SLRUStats))
+               {
+                       ereport(pgStatRunningInCollector ? LOG : WARNING,
+                                       (errmsg("corrupted statistics file 
\"%s\"", statfile)));
+                       FreeFile(fpin);
+                       return false;
+               }
+       }
+
        /* By default, we're going to return the timestamp of the global file. 
*/
        *ts = myGlobalStats.stats_timestamp;
 
@@ -6057,6 +6215,32 @@ 
pgstat_recv_resetsinglecounter(PgStat_MsgResetsinglecounter *msg, int len)
                                                   HASH_REMOVE, NULL);
 }
 
+/* ----------
+ * pgstat_recv_resetslrucounter() -
+ *
+ *     Reset some SLRU statistics of the cluster.
+ * ----------
+ */
+static void
+pgstat_recv_resetslrucounter(PgStat_MsgResetslrucounter *msg, int len)
+{
+       int                     i;
+       TimestampTz     ts = GetCurrentTimestamp();
+
+       memset(&slruStats, 0, sizeof(slruStats));
+
+       elog(LOG, "msg->m_index = %d", msg->m_index);
+
+       for (i = 0; i < SLRU_NUM_ELEMENTS; i++)
+       {
+               if ((msg->m_index == -1) || (msg->m_index == i))
+               {
+                       memset(&slruStats[i], 0, sizeof(slruStats[i]));
+                       slruStats[i].stat_reset_timestamp = ts;
+               }
+       }
+}
+
 /* ----------
  * pgstat_recv_autovac() -
  *
@@ -6201,6 +6385,24 @@ pgstat_recv_bgwriter(PgStat_MsgBgWriter *msg, int len)
        globalStats.buf_alloc += msg->m_buf_alloc;
 }
 
+/* ----------
+ * pgstat_recv_slru() -
+ *
+ *     Process a SLRU message.
+ * ----------
+ */
+static void
+pgstat_recv_slru(PgStat_MsgSLRU *msg, int len)
+{
+       slruStats[msg->m_index].blocks_zeroed += msg->m_blocks_zeroed;
+       slruStats[msg->m_index].blocks_hit += msg->m_blocks_hit;
+       slruStats[msg->m_index].blocks_read += msg->m_blocks_read;
+       slruStats[msg->m_index].blocks_written += msg->m_blocks_written;
+       slruStats[msg->m_index].blocks_exists += msg->m_blocks_exists;
+       slruStats[msg->m_index].flush += msg->m_flush;
+       slruStats[msg->m_index].truncate += msg->m_truncate;
+}
+
 /* ----------
  * pgstat_recv_recoveryconflict() -
  *
@@ -6455,3 +6657,101 @@ pgstat_clip_activity(const char *raw_activity)
 
        return activity;
 }
+
+/*
+ * pgstat_slru_index
+ *
+ * Determine index of entry for a SLRU with a given name. If there's no exact
+ * match, returns index of the last "other" entry used for SLRUs defined in
+ * external proejcts.
+ */
+int
+pgstat_slru_index(const char *name)
+{
+       int     i;
+
+       for (i = 0; i < SLRU_NUM_ELEMENTS; i++)
+       {
+               if (strcmp(slru_names[i], name) == 0)
+                       return i;
+       }
+
+       /* return index of the last entry (which is the "other" one) */
+       return (SLRU_NUM_ELEMENTS - 1);
+}
+
+/*
+ * pgstat_slru_name
+ *
+ * Returns SLRU name for an index. The index may be above SLRU_NUM_ELEMENTS,
+ * in which case this returns NULL. This allows writing code that does not
+ * know the number of entries in advance.
+ */
+char *
+pgstat_slru_name(int idx)
+{
+       Assert(idx >= 0);
+
+       if (idx >= SLRU_NUM_ELEMENTS)
+               return NULL;
+
+       return slru_names[idx];
+}
+
+/*
+ * slru_entry
+ *
+ * Returns pointer to entry with counters for given SLRU (based on the name
+ * stored in SlruCtl as lwlock tranche name).
+ */
+static PgStat_MsgSLRU *
+slru_entry(SlruCtl ctl)
+{
+       int             idx = 
pgstat_slru_index(ctl->shared->lwlock_tranche_name);
+
+       Assert((idx >= 0) && (idx < SLRU_NUM_ELEMENTS));
+
+       return &SLRUStats[idx];
+}
+
+void
+pgstat_slru_count_page_zeroed(SlruCtl ctl)
+{
+       slru_entry(ctl)->m_blocks_zeroed += 1;
+}
+
+void
+pgstat_slru_count_page_hit(SlruCtl ctl)
+{
+       slru_entry(ctl)->m_blocks_hit += 1;
+}
+
+void
+pgstat_slru_count_page_exists(SlruCtl ctl)
+{
+       slru_entry(ctl)->m_blocks_exists += 1;
+}
+
+void
+pgstat_slru_count_page_read(SlruCtl ctl)
+{
+       slru_entry(ctl)->m_blocks_read += 1;
+}
+
+void
+pgstat_slru_count_page_written(SlruCtl ctl)
+{
+       slru_entry(ctl)->m_blocks_written += 1;
+}
+
+void
+pgstat_slru_count_flush(SlruCtl ctl)
+{
+       slru_entry(ctl)->m_flush += 1;
+}
+
+void
+pgstat_slru_count_truncate(SlruCtl ctl)
+{
+       slru_entry(ctl)->m_truncate += 1;
+}
diff --git a/src/backend/utils/adt/pgstatfuncs.c 
b/src/backend/utils/adt/pgstatfuncs.c
index cea01534a5..99b20de773 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -1674,6 +1674,83 @@ pg_stat_get_buf_alloc(PG_FUNCTION_ARGS)
        PG_RETURN_INT64(pgstat_fetch_global()->buf_alloc);
 }
 
+/*
+ * Returns statistics of SLRU caches.
+ */
+Datum
+pg_stat_get_slru(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_SLRU_COLS  9
+       ReturnSetInfo  *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+       TupleDesc               tupdesc;
+       Tuplestorestate *tupstore;
+       MemoryContext   per_query_ctx;
+       MemoryContext   oldcontext;
+       int                             i;
+       PgStat_SLRUStats *stats;
+
+       /* check to see if caller supports us returning a tuplestore */
+       if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("set-valued function called in context 
that cannot accept a set")));
+       if (!(rsinfo->allowedModes & SFRM_Materialize))
+               ereport(ERROR,
+                               (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+                                errmsg("materialize mode required, but it is 
not allowed in this context")));
+
+       /* Build a tuple descriptor for our result type */
+       if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
+               elog(ERROR, "return type must be a row type");
+
+       per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
+       oldcontext = MemoryContextSwitchTo(per_query_ctx);
+
+       tupstore = tuplestore_begin_heap(true, false, work_mem);
+       rsinfo->returnMode = SFRM_Materialize;
+       rsinfo->setResult = tupstore;
+       rsinfo->setDesc = tupdesc;
+
+       MemoryContextSwitchTo(oldcontext);
+
+       /* request SLRU stats from the stat collector */
+       stats = pgstat_fetch_slru();
+
+       for (i = 0; ; i++)
+       {
+               /* for each row */
+               Datum           values[PG_STAT_GET_SLRU_COLS];
+               bool            nulls[PG_STAT_GET_SLRU_COLS];
+               PgStat_SLRUStats        stat = stats[i];
+               char       *name;
+
+               name = pgstat_slru_name(i);
+
+               if (!name)
+                       break;
+
+               MemSet(values, 0, sizeof(values));
+               MemSet(nulls, 0, sizeof(nulls));
+
+               values[0] = PointerGetDatum(cstring_to_text(name));
+               values[1] = Int64GetDatum(stat.blocks_zeroed);
+               values[2] = Int64GetDatum(stat.blocks_hit);
+               values[3] = Int64GetDatum(stat.blocks_read);
+               values[4] = Int64GetDatum(stat.blocks_written);
+               values[5] = Int64GetDatum(stat.blocks_exists);
+               values[6] = Int64GetDatum(stat.flush);
+               values[7] = Int64GetDatum(stat.truncate);
+               values[8] = Int64GetDatum(stat.stat_reset_timestamp);
+
+               tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+       }
+
+       /* clean up and return the tuplestore */
+       tuplestore_donestoring(tupstore);
+
+       return (Datum) 0;
+}
+
 Datum
 pg_stat_get_xact_numscans(PG_FUNCTION_ARGS)
 {
@@ -1919,6 +1996,20 @@ pg_stat_reset_single_function_counters(PG_FUNCTION_ARGS)
        PG_RETURN_VOID();
 }
 
+/* Reset SLRU counters (a specific one or all of them). */
+Datum
+pg_stat_reset_slru(PG_FUNCTION_ARGS)
+{
+       char       *target = NULL;
+
+       if (!PG_ARGISNULL(0))
+               target = text_to_cstring(PG_GETARG_TEXT_PP(0));
+
+       pgstat_reset_slru_counter(target);
+
+       PG_RETURN_VOID();
+}
+
 Datum
 pg_stat_get_archiver(PG_FUNCTION_ARGS)
 {
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 87d25d4a4b..96a93d8570 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5431,6 +5431,16 @@
   proname => 'pg_stat_get_buf_alloc', provolatile => 's', proparallel => 'r',
   prorettype => 'int8', proargtypes => '', prosrc => 'pg_stat_get_buf_alloc' },
 
+{ oid => '8614',
+  descr => 'statistics: information about SLRU caches',
+  proname => 'pg_stat_get_slru', prorows => '100', proisstrict => 'f',
+  proretset => 't', provolatile => 's', proparallel => 'r',
+  prorettype => 'record', proargtypes => '',
+  proallargtypes => '{text,int8,int8,int8,int8,int8,int8,int8,timestamptz}',
+  proargmodes => '{o,o,o,o,o,o,o,o,o}',
+  proargnames => 
'{name,blks_zeroed,blks_hit,blks_read,blks_written,blks_exists,flushes,truncates,stats_reset}',
+  prosrc => 'pg_stat_get_slru' },
+
 { oid => '2978', descr => 'statistics: number of function calls',
   proname => 'pg_stat_get_function_calls', provolatile => 's',
   proparallel => 'r', prorettype => 'int8', proargtypes => 'oid',
@@ -5535,6 +5545,11 @@
   proname => 'pg_stat_reset_single_function_counters', provolatile => 'v',
   prorettype => 'void', proargtypes => 'oid',
   prosrc => 'pg_stat_reset_single_function_counters' },
+{ oid => '4179',
+  descr => 'statistics: reset collected statistics for a single SLRU',
+  proname => 'pg_stat_reset_slru', provolatile => 'v', proisstrict => 'f',
+  prorettype => 'void', proargtypes => 'text',
+  prosrc => 'pg_stat_reset_slru' },
 
 { oid => '3163', descr => 'current trigger depth',
   proname => 'pg_trigger_depth', provolatile => 's', proparallel => 'r',
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index a07012bf4b..0fac192ea1 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -11,6 +11,7 @@
 #ifndef PGSTAT_H
 #define PGSTAT_H
 
+#include "access/slru.h"
 #include "datatype/timestamp.h"
 #include "libpq/pqcomm.h"
 #include "miscadmin.h"
@@ -55,11 +56,13 @@ typedef enum StatMsgType
        PGSTAT_MTYPE_RESETCOUNTER,
        PGSTAT_MTYPE_RESETSHAREDCOUNTER,
        PGSTAT_MTYPE_RESETSINGLECOUNTER,
+       PGSTAT_MTYPE_RESETSLRUCOUNTER,
        PGSTAT_MTYPE_AUTOVAC_START,
        PGSTAT_MTYPE_VACUUM,
        PGSTAT_MTYPE_ANALYZE,
        PGSTAT_MTYPE_ARCHIVER,
        PGSTAT_MTYPE_BGWRITER,
+       PGSTAT_MTYPE_SLRU,
        PGSTAT_MTYPE_FUNCSTAT,
        PGSTAT_MTYPE_FUNCPURGE,
        PGSTAT_MTYPE_RECOVERYCONFLICT,
@@ -343,6 +346,17 @@ typedef struct PgStat_MsgResetsinglecounter
        Oid                     m_objectid;
 } PgStat_MsgResetsinglecounter;
 
+/* ----------
+ * PgStat_MsgResetslrucounter Sent by the backend to tell the collector
+ *                                                             to reset a SLRU 
counter
+ * ----------
+ */
+typedef struct PgStat_MsgResetslrucounter
+{
+       PgStat_MsgHdr m_hdr;
+       int                     m_index;
+} PgStat_MsgResetslrucounter;
+
 /* ----------
  * PgStat_MsgAutovacStart              Sent by the autovacuum daemon to signal
  *                                                             that a database 
is going to be processed
@@ -423,6 +437,23 @@ typedef struct PgStat_MsgBgWriter
        PgStat_Counter m_checkpoint_sync_time;
 } PgStat_MsgBgWriter;
 
+/* ----------
+ * PgStat_MsgSLRU                      Sent by the SLRU to update statistics.
+ * ----------
+ */
+typedef struct PgStat_MsgSLRU
+{
+       PgStat_MsgHdr m_hdr;
+       PgStat_Counter m_index;
+       PgStat_Counter m_blocks_zeroed;
+       PgStat_Counter m_blocks_hit;
+       PgStat_Counter m_blocks_read;
+       PgStat_Counter m_blocks_written;
+       PgStat_Counter m_blocks_exists;
+       PgStat_Counter m_flush;
+       PgStat_Counter m_truncate;
+} PgStat_MsgSLRU;
+
 /* ----------
  * PgStat_MsgRecoveryConflict  Sent by the backend upon recovery conflict
  * ----------
@@ -560,11 +591,13 @@ typedef union PgStat_Msg
        PgStat_MsgResetcounter msg_resetcounter;
        PgStat_MsgResetsharedcounter msg_resetsharedcounter;
        PgStat_MsgResetsinglecounter msg_resetsinglecounter;
+       PgStat_MsgResetslrucounter msg_resetslrucounter;
        PgStat_MsgAutovacStart msg_autovacuum_start;
        PgStat_MsgVacuum msg_vacuum;
        PgStat_MsgAnalyze msg_analyze;
        PgStat_MsgArchiver msg_archiver;
        PgStat_MsgBgWriter msg_bgwriter;
+       PgStat_MsgSLRU msg_slru;
        PgStat_MsgFuncstat msg_funcstat;
        PgStat_MsgFuncpurge msg_funcpurge;
        PgStat_MsgRecoveryConflict msg_recoveryconflict;
@@ -712,6 +745,21 @@ typedef struct PgStat_GlobalStats
        TimestampTz stat_reset_timestamp;
 } PgStat_GlobalStats;
 
+/*
+ * SLRU statistics kept in the stats collector
+ */
+typedef struct PgStat_SLRUStats
+{
+       PgStat_Counter blocks_zeroed;
+       PgStat_Counter blocks_hit;
+       PgStat_Counter blocks_read;
+       PgStat_Counter blocks_written;
+       PgStat_Counter blocks_exists;
+       PgStat_Counter flush;
+       PgStat_Counter truncate;
+       TimestampTz stat_reset_timestamp;
+} PgStat_SLRUStats;
+
 
 /* ----------
  * Backend states
@@ -1209,6 +1257,11 @@ extern char *pgstat_stat_filename;
  */
 extern PgStat_MsgBgWriter BgWriterStats;
 
+/*
+ * SLRU statistics counters are updated directly by slru.
+ */
+extern PgStat_MsgSLRU SlruStats[];
+
 /*
  * Updated by pgstat_count_buffer_*_time macros
  */
@@ -1246,6 +1299,7 @@ extern void pgstat_clear_snapshot(void);
 extern void pgstat_reset_counters(void);
 extern void pgstat_reset_shared_counters(const char *);
 extern void pgstat_reset_single_counter(Oid objectid, PgStat_Single_Reset_Type 
type);
+extern void pgstat_reset_slru_counter(const char *);
 
 extern void pgstat_report_autovac(Oid dboid);
 extern void pgstat_report_vacuum(Oid tableoid, bool shared,
@@ -1421,5 +1475,16 @@ extern PgStat_StatFuncEntry 
*pgstat_fetch_stat_funcentry(Oid funcid);
 extern int     pgstat_fetch_stat_numbackends(void);
 extern PgStat_ArchiverStats *pgstat_fetch_stat_archiver(void);
 extern PgStat_GlobalStats *pgstat_fetch_global(void);
+extern PgStat_SLRUStats *pgstat_fetch_slru(void);
+
+extern void pgstat_slru_count_page_zeroed(SlruCtl ctl);
+extern void pgstat_slru_count_page_hit(SlruCtl ctl);
+extern void pgstat_slru_count_page_read(SlruCtl ctl);
+extern void pgstat_slru_count_page_written(SlruCtl ctl);
+extern void pgstat_slru_count_page_exists(SlruCtl ctl);
+extern void pgstat_slru_count_flush(SlruCtl ctl);
+extern void pgstat_slru_count_truncate(SlruCtl ctl);
+extern char *pgstat_slru_name(int idx);
+extern int pgstat_slru_index(const char *name);
 
 #endif                                                 /* PGSTAT_H */
diff --git a/src/test/regress/expected/rules.out 
b/src/test/regress/expected/rules.out
index a2077bbad4..798364230e 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -2006,6 +2006,16 @@ pg_stat_replication| SELECT s.pid,
    FROM ((pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, 
application_name, state, query, wait_event_type, wait_event, xact_start, 
query_start, backend_start, state_change, client_addr, client_hostname, 
client_port, backend_xid, backend_xmin, backend_type, ssl, sslversion, 
sslcipher, sslbits, sslcompression, ssl_client_dn, ssl_client_serial, 
ssl_issuer_dn, gss_auth, gss_princ, gss_enc, leader_pid)
      JOIN pg_stat_get_wal_senders() w(pid, state, sent_lsn, write_lsn, 
flush_lsn, replay_lsn, write_lag, flush_lag, replay_lag, sync_priority, 
sync_state, reply_time, spill_txns, spill_count, spill_bytes) ON ((s.pid = 
w.pid)))
      LEFT JOIN pg_authid u ON ((s.usesysid = u.oid)));
+pg_stat_slru| SELECT s.name,
+    s.blks_zeroed,
+    s.blks_hit,
+    s.blks_read,
+    s.blks_written,
+    s.blks_exists,
+    s.flushes,
+    s.truncates,
+    s.stats_reset
+   FROM pg_stat_get_slru() s(name, blks_zeroed, blks_hit, blks_read, 
blks_written, blks_exists, flushes, truncates, stats_reset);
 pg_stat_ssl| SELECT s.pid,
     s.ssl,
     s.sslversion AS version,
-- 
2.21.1

Reply via email to