Hi,

One of the stats I occasionally wanted to know are stats for the SLRU
stats (we have couple of those - clog, subtrans, ...). So here is a WIP
version of a patch adding that.

The implementation is fairly simple - the slru code updates counters in
local memory, and then sends them to the collector at the end of the
transaction (similarly to table/func stats). The collector stores it
similarly to global stats. And the collected stats are accessible
through pg_stat_slru.

The main issue is that we have multiple SLRU caches, and it seems handy
to have separate stats for each. OTOH the number of SLRU stats is not
fixed, so e.g. extensions might define their own SLRU caches. But
handing dynamic number of SLRU caches seems a bit hard (we'd need to
assign some sort of unique IDs etc.) so what I did was define a fixed
number of SLRU types

    typedef enum SlruType
    {
        SLRU_CLOG,
        SLRU_COMMIT_TS,
        SLRU_MULTIXACT_OFFSET,
        SLRU_MULTIXACT_MEMBER,
        SLRU_SUBTRANS,
        SLRU_ASYNC,
        SLRU_OLDSERXID,
        SLRU_OTHER
    } SlruType;

with one group of counters for each group. The last type (SLRU_OTHER) is
used to store stats for all SLRUs that are not predefined. It wouldn't
be that difficult to store dynamic number of SLRUs, but I'm not sure how
to solve issues with identifying SLRUs etc. And there are probably very
few extensions adding custom SLRU anyway.

The one thing missing from the patch is a way to reset the SLRU stats,
similarly to how we can reset bgwriter stats.


regards

--
Tomas Vondra                  http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
diff --git a/src/backend/access/transam/clog.c 
b/src/backend/access/transam/clog.c
index f8e7670f8d..3f45db7ea9 100644
--- a/src/backend/access/transam/clog.c
+++ b/src/backend/access/transam/clog.c
@@ -692,7 +692,8 @@ CLOGShmemInit(void)
 {
        ClogCtl->PagePrecedes = CLOGPagePrecedes;
        SimpleLruInit(ClogCtl, "clog", CLOGShmemBuffers(), CLOG_LSNS_PER_PAGE,
-                                 CLogControlLock, "pg_xact", 
LWTRANCHE_CLOG_BUFFERS);
+                                 CLogControlLock, "pg_xact", 
LWTRANCHE_CLOG_BUFFERS,
+                                 SLRU_CLOG);
 }
 
 /*
diff --git a/src/backend/access/transam/commit_ts.c 
b/src/backend/access/transam/commit_ts.c
index 630df672cc..44d7ca4483 100644
--- a/src/backend/access/transam/commit_ts.c
+++ b/src/backend/access/transam/commit_ts.c
@@ -494,7 +494,7 @@ CommitTsShmemInit(void)
        CommitTsCtl->PagePrecedes = CommitTsPagePrecedes;
        SimpleLruInit(CommitTsCtl, "commit_timestamp", CommitTsShmemBuffers(), 
0,
                                  CommitTsControlLock, "pg_commit_ts",
-                                 LWTRANCHE_COMMITTS_BUFFERS);
+                                 LWTRANCHE_COMMITTS_BUFFERS, SLRU_COMMIT_TS);
 
        commitTsShared = ShmemInitStruct("CommitTs shared",
                                                                         
sizeof(CommitTimestampShared),
diff --git a/src/backend/access/transam/multixact.c 
b/src/backend/access/transam/multixact.c
index 50e98caaeb..37a5854284 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -1831,11 +1831,11 @@ MultiXactShmemInit(void)
        SimpleLruInit(MultiXactOffsetCtl,
                                  "multixact_offset", NUM_MXACTOFFSET_BUFFERS, 
0,
                                  MultiXactOffsetControlLock, 
"pg_multixact/offsets",
-                                 LWTRANCHE_MXACTOFFSET_BUFFERS);
+                                 LWTRANCHE_MXACTOFFSET_BUFFERS, 
SLRU_MULTIXACT_OFFSET);
        SimpleLruInit(MultiXactMemberCtl,
                                  "multixact_member", NUM_MXACTMEMBER_BUFFERS, 
0,
                                  MultiXactMemberControlLock, 
"pg_multixact/members",
-                                 LWTRANCHE_MXACTMEMBER_BUFFERS);
+                                 LWTRANCHE_MXACTMEMBER_BUFFERS, 
SLRU_MULTIXACT_MEMBER);
 
        /* Initialize our shared state struct */
        MultiXactState = ShmemInitStruct("Shared MultiXact State",
diff --git a/src/backend/access/transam/slru.c 
b/src/backend/access/transam/slru.c
index d5b7a08f73..dab15e6ab4 100644
--- a/src/backend/access/transam/slru.c
+++ b/src/backend/access/transam/slru.c
@@ -162,7 +162,8 @@ SimpleLruShmemSize(int nslots, int nlsns)
 
 void
 SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
-                         LWLock *ctllock, const char *subdir, int tranche_id)
+                         LWLock *ctllock, const char *subdir, int tranche_id,
+                         SlruType type)
 {
        SlruShared      shared;
        bool            found;
@@ -247,6 +248,7 @@ SimpleLruInit(SlruCtl ctl, const char *name, int nslots, 
int nlsns,
         */
        ctl->shared = shared;
        ctl->do_fsync = true;           /* default behavior */
+       ctl->type = type;
        StrNCpy(ctl->Dir, subdir, sizeof(ctl->Dir));
 }
 
@@ -286,6 +288,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_count_slru_zero_page(ctl);
+
        return slotno;
 }
 
@@ -403,9 +408,16 @@ 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 shared 
buffers */
+                       pgstat_count_slru_page_hit(ctl);
+
                        return slotno;
                }
 
+               /* update the stats counter of pages not found */
+               pgstat_count_slru_page_miss(ctl);
+
                /* We found no match; assert we selected a freeable slot */
                Assert(shared->page_status[slotno] == SLRU_PAGE_EMPTY ||
                           (shared->page_status[slotno] == SLRU_PAGE_VALID &&
@@ -596,6 +608,9 @@ SimpleLruDoesPhysicalPageExist(SlruCtl ctl, int pageno)
        bool            result;
        off_t           endpos;
 
+       /* update the stats counter of checked pages */
+       pgstat_count_slru_page_exists(ctl);
+
        SlruFileName(ctl, path, segno);
 
        fd = OpenTransientFile(path, O_RDONLY | PG_BINARY);
@@ -650,6 +665,9 @@ SlruPhysicalReadPage(SlruCtl ctl, int pageno, int slotno)
        char            path[MAXPGPATH];
        int                     fd;
 
+       /* update the stats counter of read pages */
+       pgstat_count_slru_page_read(ctl);
+
        SlruFileName(ctl, path, segno);
 
        /*
@@ -730,6 +748,9 @@ SlruPhysicalWritePage(SlruCtl ctl, int pageno, int slotno, 
SlruFlush fdata)
        char            path[MAXPGPATH];
        int                     fd = -1;
 
+       /* update the stats counter of written pages */
+       pgstat_count_slru_page_write(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
@@ -901,6 +922,9 @@ SlruReportIOError(SlruCtl ctl, int pageno, TransactionId 
xid)
        int                     offset = rpageno * BLCKSZ;
        char            path[MAXPGPATH];
 
+       /* update the stats counter of errors */
+       pgstat_count_slru_io_error(ctl);
+
        SlruFileName(ctl, path, segno);
        errno = slru_errno;
        switch (slru_errcause)
@@ -1125,6 +1149,9 @@ SimpleLruFlush(SlruCtl ctl, bool allow_redirtied)
        int                     i;
        bool            ok;
 
+       /* update the stats counter of flushes */
+       pgstat_count_slru_flush(ctl);
+
        /*
         * Find and write dirty pages
         */
@@ -1186,6 +1213,9 @@ SimpleLruTruncate(SlruCtl ctl, int cutoffPage)
        SlruShared      shared = ctl->shared;
        int                     slotno;
 
+       /* update the stats counter of truncates */
+       pgstat_count_slru_truncate(ctl);
+
        /*
         * The cutoff point is the start of the segment containing cutoffPage.
         */
diff --git a/src/backend/access/transam/subtrans.c 
b/src/backend/access/transam/subtrans.c
index 25d7d739cf..3316accb50 100644
--- a/src/backend/access/transam/subtrans.c
+++ b/src/backend/access/transam/subtrans.c
@@ -193,7 +193,7 @@ SUBTRANSShmemInit(void)
        SubTransCtl->PagePrecedes = SubTransPagePrecedes;
        SimpleLruInit(SubTransCtl, "subtrans", NUM_SUBTRANS_BUFFERS, 0,
                                  SubtransControlLock, "pg_subtrans",
-                                 LWTRANCHE_SUBTRANS_BUFFERS);
+                                 LWTRANCHE_SUBTRANS_BUFFERS, SLRU_SUBTRANS);
        /* Override default assumption that writes should be fsync'd */
        SubTransCtl->do_fsync = false;
 }
diff --git a/src/backend/catalog/system_views.sql 
b/src/backend/catalog/system_views.sql
index c9e75f4370..1844f56859 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -790,6 +790,20 @@ 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.pages_zero,
+            s.pages_hit,
+            s.pages_miss,
+            s.pages_exists,
+            s.pages_read,
+            s.pages_write,
+            s.io_error,
+            s.flushes,
+            s.truncates
+    FROM pg_stat_get_slru() s;
+
 CREATE VIEW pg_stat_wal_receiver AS
     SELECT
             s.pid,
diff --git a/src/backend/commands/async.c b/src/backend/commands/async.c
index 9aa2b61600..02222cc92a 100644
--- a/src/backend/commands/async.c
+++ b/src/backend/commands/async.c
@@ -552,7 +552,8 @@ AsyncShmemInit(void)
         */
        AsyncCtl->PagePrecedes = asyncQueuePagePrecedes;
        SimpleLruInit(AsyncCtl, "async", NUM_ASYNC_BUFFERS, 0,
-                                 AsyncCtlLock, "pg_notify", 
LWTRANCHE_ASYNC_BUFFERS);
+                                 AsyncCtlLock, "pg_notify", 
LWTRANCHE_ASYNC_BUFFERS,
+                                 SLRU_ASYNC);
        /* Override default assumption that writes should be fsync'd */
        AsyncCtl->do_fsync = false;
 
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index 51c486bebd..aa825029b1 100644
--- a/src/backend/postmaster/pgstat.c
+++ b/src/backend/postmaster/pgstat.c
@@ -141,6 +141,9 @@ char           *pgstat_stat_tmpname = NULL;
  */
 PgStat_MsgBgWriter BgWriterStats;
 
+/* TODO */
+PgStat_MsgSlru SlruStats[SLRU_OTHER + 1];
+
 /* ----------
  * Local data
  * ----------
@@ -255,6 +258,7 @@ static int  localNumBackends = 0;
  */
 static PgStat_ArchiverStats archiverStats;
 static PgStat_GlobalStats globalStats;
+static PgStat_SlruStats slruStats[SLRU_OTHER + 1];
 
 /*
  * List of OIDs of databases we need to write out.  If an entry is InvalidOid,
@@ -297,6 +301,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);
@@ -324,6 +329,7 @@ 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);
@@ -904,6 +910,9 @@ pgstat_report_stat(bool force)
 
        /* Now, send function statistics */
        pgstat_send_funcstats();
+
+       /* Finally send SLRU statistics */
+       pgstat_send_slru();
 }
 
 /*
@@ -2619,6 +2628,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
  * ------------------------------------------------------------
@@ -4410,6 +4436,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_OTHER; 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_type = 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() -
@@ -4602,6 +4668,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;
@@ -4832,6 +4902,7 @@ pgstat_write_statsfiles(bool permanent, bool allDbs)
        const char *tmpfile = permanent ? PGSTAT_STAT_PERMANENT_TMPFILE : 
pgstat_stat_tmpname;
        const char *statfile = permanent ? PGSTAT_STAT_PERMANENT_FILENAME : 
pgstat_stat_filename;
        int                     rc;
+       int                     i;
 
        elog(DEBUG2, "writing stats file \"%s\"", statfile);
 
@@ -4872,6 +4943,15 @@ 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
+        */
+       for (i = 0; i <= SLRU_OTHER; i++)
+       {
+               rc = fwrite(&slruStats[i], sizeof(PgStat_SlruStats), 1, fpout);
+               (void) rc;                                      /* we'll check 
for error with ferror */
+       }
+
        /*
         * Walk through the database table.
         */
@@ -5107,6 +5187,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.
@@ -5129,6 +5210,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
@@ -5199,6 +5281,20 @@ pgstat_read_statsfiles(Oid onlydb, bool permanent, bool 
deep)
                goto done;
        }
 
+       /*
+        * Read SLRU stats struct
+        */
+       for (i = 0; i <= SLRU_OTHER; i++)
+       {
+               if (fread(&slruStats[i], 1, sizeof(PgStat_SlruStats), fpin) != 
sizeof(PgStat_SlruStats))
+               {
+                       ereport(pgStatRunningInCollector ? LOG : WARNING,
+                                       (errmsg("corrupted statistics file 
\"%s\"", statfile)));
+                       memset(&slruStats[i], 0, sizeof(PgStat_SlruStats));
+                       goto done;
+               }
+       }
+
        /*
         * We found an existing collector stats file. Read it and put all the
         * hashtable entries into place.
@@ -5497,9 +5593,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
@@ -5551,6 +5649,21 @@ pgstat_read_db_statsfile_timestamp(Oid databaseid, bool 
permanent,
                return false;
        }
 
+       /*
+        * Read SLRU stats struct
+        */
+       for (i = 0; i <= SLRU_OTHER; 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;
 
@@ -6292,6 +6405,26 @@ 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_type].pages_zero += msg->m_pages_zero;
+       slruStats[msg->m_type].pages_hit += msg->m_pages_hit;
+       slruStats[msg->m_type].pages_miss += msg->m_pages_miss;
+       slruStats[msg->m_type].pages_exists += msg->m_pages_exists;
+       slruStats[msg->m_type].pages_read += msg->m_pages_read;
+       slruStats[msg->m_type].pages_write += msg->m_pages_write;
+       slruStats[msg->m_type].io_error += msg->m_io_error;
+       slruStats[msg->m_type].flush += msg->m_flush;
+       slruStats[msg->m_type].truncate += msg->m_truncate;
+}
+
 /* ----------
  * pgstat_recv_recoveryconflict() -
  *
@@ -6546,3 +6679,57 @@ pgstat_clip_activity(const char *raw_activity)
 
        return activity;
 }
+
+void
+pgstat_count_slru_zero_page(SlruCtl ctl)
+{
+       SlruStats[ctl->type].m_pages_zero += 1;
+}
+
+void
+pgstat_count_slru_page_hit(SlruCtl ctl)
+{
+       SlruStats[ctl->type].m_pages_hit += 1;
+}
+
+void
+pgstat_count_slru_page_miss(SlruCtl ctl)
+{
+       SlruStats[ctl->type].m_pages_miss += 1;
+}
+
+void
+pgstat_count_slru_page_exists(SlruCtl ctl)
+{
+       SlruStats[ctl->type].m_pages_exists += 1;
+}
+
+void
+pgstat_count_slru_page_read(SlruCtl ctl)
+{
+       SlruStats[ctl->type].m_pages_read += 1;
+}
+
+void
+pgstat_count_slru_page_write(SlruCtl ctl)
+{
+       SlruStats[ctl->type].m_pages_write += 1;
+}
+
+void
+pgstat_count_slru_io_error(SlruCtl ctl)
+{
+       SlruStats[ctl->type].m_io_error += 1;
+}
+
+void
+pgstat_count_slru_flush(SlruCtl ctl)
+{
+       SlruStats[ctl->type].m_flush += 1;
+}
+
+void
+pgstat_count_slru_truncate(SlruCtl ctl)
+{
+       SlruStats[ctl->type].m_truncate += 1;
+}
diff --git a/src/backend/storage/lmgr/predicate.c 
b/src/backend/storage/lmgr/predicate.c
index de46b841cb..82e5d7ec57 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -824,7 +824,7 @@ OldSerXidInit(void)
        OldSerXidSlruCtl->PagePrecedes = OldSerXidPagePrecedesLogically;
        SimpleLruInit(OldSerXidSlruCtl, "oldserxid",
                                  NUM_OLDSERXID_BUFFERS, 0, OldSerXidLock, 
"pg_serial",
-                                 LWTRANCHE_OLDSERXID_BUFFERS);
+                                 LWTRANCHE_OLDSERXID_BUFFERS, SLRU_OLDSERXID);
        /* Override default assumption that writes should be fsync'd */
        OldSerXidSlruCtl->do_fsync = false;
 
diff --git a/src/backend/utils/adt/pgstatfuncs.c 
b/src/backend/utils/adt/pgstatfuncs.c
index 74f899f24d..8233568800 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -1665,6 +1665,96 @@ 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  10
+       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 <= SLRU_OTHER; i++)
+       {
+               /* for each row */
+               Datum           values[PG_STAT_GET_SLRU_COLS];
+               bool            nulls[PG_STAT_GET_SLRU_COLS];
+               PgStat_SlruStats        stat = stats[i];
+               text       *name;
+
+               MemSet(values, 0, sizeof(values));
+               MemSet(nulls, 0, sizeof(nulls));
+
+               if (i == SLRU_CLOG)
+                       name = cstring_to_text("clog");
+               else if (i == SLRU_COMMIT_TS)
+                       name = cstring_to_text("commit_timestamp");
+               else if (i == SLRU_MULTIXACT_OFFSET)
+                       name = cstring_to_text("multixact_offset");
+               else if (i == SLRU_MULTIXACT_MEMBER)
+                       name = cstring_to_text("multixact_member");
+               else if (i == SLRU_SUBTRANS)
+                       name = cstring_to_text("subtrans");
+               else if (i == SLRU_ASYNC)
+                       name = cstring_to_text("async");
+               else if (i == SLRU_OLDSERXID)
+                       name = cstring_to_text("oldserxid");
+               else if (i == SLRU_OTHER)
+                       name = cstring_to_text("other");
+
+               values[0] = PointerGetDatum(name);
+               values[1] = Int64GetDatum(stat.pages_zero);
+               values[2] = Int64GetDatum(stat.pages_hit);
+               values[3] = Int64GetDatum(stat.pages_miss);
+               values[4] = Int64GetDatum(stat.pages_exists);
+               values[5] = Int64GetDatum(stat.pages_read);
+               values[6] = Int64GetDatum(stat.pages_write);
+               values[7] = Int64GetDatum(stat.io_error);
+               values[8] = Int64GetDatum(stat.flush);
+               values[9] = Int64GetDatum(stat.truncate);
+
+               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)
 {
diff --git a/src/include/access/slru.h b/src/include/access/slru.h
index 00dbd803e1..eb79ab346f 100644
--- a/src/include/access/slru.h
+++ b/src/include/access/slru.h
@@ -106,6 +106,18 @@ typedef struct SlruSharedData
 
 typedef SlruSharedData *SlruShared;
 
+typedef enum SlruType
+{
+       SLRU_CLOG,
+       SLRU_COMMIT_TS,
+       SLRU_MULTIXACT_OFFSET,
+       SLRU_MULTIXACT_MEMBER,
+       SLRU_SUBTRANS,
+       SLRU_ASYNC,
+       SLRU_OLDSERXID,
+       SLRU_OTHER
+} SlruType;
+
 /*
  * SlruCtlData is an unshared structure that points to the active information
  * in shared memory.
@@ -114,6 +126,9 @@ typedef struct SlruCtlData
 {
        SlruShared      shared;
 
+       /* type of the SLRU */
+       SlruType        type;
+
        /*
         * This flag tells whether to fsync writes (true for pg_xact and 
multixact
         * stuff, false for pg_subtrans and pg_notify).
@@ -139,7 +154,8 @@ typedef SlruCtlData *SlruCtl;
 
 extern Size SimpleLruShmemSize(int nslots, int nlsns);
 extern void SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
-                                                 LWLock *ctllock, const char 
*subdir, int tranche_id);
+                                                 LWLock *ctllock, const char 
*subdir, int tranche_id,
+                                                 SlruType type);
 extern int     SimpleLruZeroPage(SlruCtl ctl, int pageno);
 extern int     SimpleLruReadPage(SlruCtl ctl, int pageno, bool write_ok,
                                                          TransactionId xid);
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index fcf2a1214c..eadd68e409 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -5408,6 +5408,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,int8,int8}',
+  proargmodes => '{o,o,o,o,o,o,o,o,o,o}',
+  proargnames => 
'{name,pages_zero,pages_hit,pages_miss,pages_exists,pages_read,pages_write,io_error,flushes,truncates}',
+  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',
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 36b530bc27..677ce2a87a 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 "port/atomics.h"
@@ -59,6 +60,7 @@ typedef enum StatMsgType
        PGSTAT_MTYPE_ANALYZE,
        PGSTAT_MTYPE_ARCHIVER,
        PGSTAT_MTYPE_BGWRITER,
+       PGSTAT_MTYPE_SLRU,
        PGSTAT_MTYPE_FUNCSTAT,
        PGSTAT_MTYPE_FUNCPURGE,
        PGSTAT_MTYPE_RECOVERYCONFLICT,
@@ -422,6 +424,25 @@ 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_type;
+       PgStat_Counter m_pages_zero;
+       PgStat_Counter m_pages_hit;
+       PgStat_Counter m_pages_miss;
+       PgStat_Counter m_pages_exists;
+       PgStat_Counter m_pages_read;
+       PgStat_Counter m_pages_write;
+       PgStat_Counter m_io_error;
+       PgStat_Counter m_flush;
+       PgStat_Counter m_truncate;
+} PgStat_MsgSlru;
+
 /* ----------
  * PgStat_MsgRecoveryConflict  Sent by the backend upon recovery conflict
  * ----------
@@ -564,6 +585,7 @@ typedef union PgStat_Msg
        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;
@@ -711,6 +733,22 @@ typedef struct PgStat_GlobalStats
        TimestampTz stat_reset_timestamp;
 } PgStat_GlobalStats;
 
+/*
+ * Slru statistics kept in the stats collector
+ */
+typedef struct PgStat_SlruStats
+{
+       PgStat_Counter pages_zero;
+       PgStat_Counter pages_hit;
+       PgStat_Counter pages_miss;
+       PgStat_Counter pages_exists;
+       PgStat_Counter pages_read;
+       PgStat_Counter pages_write;
+       PgStat_Counter io_error;
+       PgStat_Counter flush;
+       PgStat_Counter truncate;
+} PgStat_SlruStats;
+
 
 /* ----------
  * Backend types
@@ -1223,6 +1261,11 @@ extern char *pgstat_stat_filename;
  */
 extern PgStat_MsgBgWriter BgWriterStats;
 
+/*
+ * SLRU statistics counters are updated directly by slru.
+ */
+extern PgStat_MsgSlru SlruStats[SLRU_OTHER + 1];
+
 /*
  * Updated by pgstat_count_buffer_*_time macros
  */
@@ -1436,5 +1479,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_count_slru_zero_page(SlruCtl ctl);
+extern void pgstat_count_slru_page_hit(SlruCtl ctl);
+extern void pgstat_count_slru_page_miss(SlruCtl ctl);
+extern void pgstat_count_slru_page_exists(SlruCtl ctl);
+extern void pgstat_count_slru_page_read(SlruCtl ctl);
+extern void pgstat_count_slru_page_write(SlruCtl ctl);
+extern void pgstat_count_slru_io_error(SlruCtl ctl);
+extern void pgstat_count_slru_flush(SlruCtl ctl);
+extern void pgstat_count_slru_truncate(SlruCtl ctl);
 
 #endif                                                 /* PGSTAT_H */

Reply via email to