On Sat, Feb 29, 2020 at 11:44:26AM -0300, Alvaro Herrera wrote:
On 2020-Feb-29, Tomas Vondra wrote:

Did we actually remove track-enabling GUCs? I think we still have

 - track_activities
 - track_counts
 - track_io_timing
 - track_functions

But maybe I'm missing something?

Hm I remembered we removed the one for row-level stats
(track_row_stats), but what we really did is merge it with block-level
stats (track_block_stats) into track_counts -- commit 48f7e6439568.
Funnily enough, if you disable that autovacuum won't work, so I'm not
sure it's a very useful tunable.  And it definitely has more overhead
than what this new GUC would have.


OK

> I find SlruType pretty odd, and the accompanying "if" list in
> pg_stat_get_slru() correspondingly so.  Would it be possible to have
> each SLRU enumerate itself somehow?  Maybe add the name in SlruCtlData
> and query that, somehow.  (I don't think we have an array of SlruCtlData
> anywhere though, so this might be a useless idea.)

Well, maybe. We could have a system to register SLRUs dynamically, but
the trick here is that by having a fixed predefined number of SLRUs
simplifies serialization in pgstat.c and so on. I don't think the "if"
branches in pg_stat_get_slru() are particularly ugly, but maybe we could
replace the enum with a registry of structs, something like rmgrlist.h.
It seems like an overkill to me, though.

Yeah, maybe we don't have to fix that now.


IMO the current solution is sufficient for the purpose. I guess we could
just stick a name into the SlruCtlData (and remove SlruType entirely),
and use that to identify the stats entries. That might be enough, and in
fact we already have that - SimpleLruInit gets a name parameter and
copies that to the lwlock_tranche_name.

One of the main reasons why I opted to use the enum is that it makes
tracking, lookup and serialization pretty trivial - it's just an index
lookup, etc. But maybe it wouldn't be much more complex with the name, considering the name length is limited by SLRU_MAX_NAME_LENGTH. And we
probably don't expect many entries, so we could keep them in a simple
list, or maybe a simplehash.

I'm not sure what to do with data for SLRUs that might have disappeared
after a restart (e.g. because someone removed an extension). Until now
those would be in the all in the "other" entry.


The attached v2 fixes the issues in your first message:

- I moved the page_miss() call after SlruRecentlyUsed(), but then I
  realized it's entirely duplicate with the page_read() update done in
  SlruPhysicalReadPage(). I removed the call from SlruPhysicalReadPage()
  and renamed page_miss to page_read - that's more consistent with
  shared buffers stats, which also have buffers_hit and buffer_read.

- I've also implemented the reset. I ended up adding a new option to
  pg_stat_reset_shared, which always resets all SLRU entries. We track
  the reset timestamp for each SLRU entry, but the value is always the
  same. I admit this is a bit weird - I did it like this because (a) I'm
  not sure how to identify the individual entries and (b) the SLRU is
  shared, so pg_stat_reset_shared seems kinda natural.


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..0c3f8b9251 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,6 +408,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_count_slru_page_hit(ctl);
+
                        return slotno;
                }
 
@@ -444,6 +453,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_count_slru_page_read(ctl);
+
                return slotno;
        }
 }
@@ -596,6 +609,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);
@@ -730,6 +746,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 +920,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 +1147,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 +1211,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 f681aafcf9..10677ff778 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -792,6 +792,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_read,
+            s.pages_write,
+            s.pages_exists,
+            s.io_error,
+            s.flushes,
+            s.truncates,
+            s.stat_reset
+    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 dae939a4ab..f442125ead 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 462b4d7e06..37e1312fa2 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);
@@ -907,6 +913,9 @@ pgstat_report_stat(bool force)
 
        /* Now, send function statistics */
        pgstat_send_funcstats();
+
+       /* Finally send SLRU statistics */
+       pgstat_send_slru();
 }
 
 /*
@@ -1337,6 +1346,8 @@ pgstat_reset_shared_counters(const char *target)
                msg.m_resettarget = RESET_ARCHIVER;
        else if (strcmp(target, "bgwriter") == 0)
                msg.m_resettarget = RESET_BGWRITER;
+       else if (strcmp(target, "slru") == 0)
+               msg.m_resettarget = RESET_SLRU;
        else
                ereport(ERROR,
                                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -2622,6 +2633,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
  * ------------------------------------------------------------
@@ -4413,6 +4441,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() -
@@ -4603,6 +4671,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;
@@ -4831,6 +4903,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);
 
@@ -4871,6 +4944,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.
         */
@@ -5106,6 +5188,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.
@@ -5128,6 +5211,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
@@ -5136,6 +5220,12 @@ 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.
+        */
+       for (i = 0; i <= SLRU_OTHER; 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
@@ -5198,6 +5288,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.
@@ -5496,9 +5600,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
@@ -5550,6 +5656,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;
 
@@ -6112,6 +6233,17 @@ 
pgstat_recv_resetsharedcounter(PgStat_MsgResetsharedcounter *msg, int len)
                memset(&archiverStats, 0, sizeof(archiverStats));
                archiverStats.stat_reset_timestamp = GetCurrentTimestamp();
        }
+       else if (msg->m_resettarget == RESET_SLRU)
+       {
+               int                     i;
+               TimestampTz     ts = GetCurrentTimestamp();
+
+               /* Reset the SLRU statistics for the cluster. */
+               memset(&slruStats, 0, sizeof(slruStats));
+
+               for (i = 0; i <= SLRU_OTHER; i++)
+                       slruStats[i].stat_reset_timestamp = ts;
+       }
 
        /*
         * Presumably the sender of this message validated the target, don't
@@ -6291,6 +6423,25 @@ 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_read += msg->m_pages_read;
+       slruStats[msg->m_type].pages_write += msg->m_pages_write;
+       slruStats[msg->m_type].pages_exists += msg->m_pages_exists;
+       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() -
  *
@@ -6545,3 +6696,51 @@ 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_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 654584b77a..53164bdff3 100644
--- a/src/backend/storage/lmgr/predicate.c
+++ b/src/backend/storage/lmgr/predicate.c
@@ -821,7 +821,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 7e6a3c1774..40b56e0cd0 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -1672,6 +1672,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_read);
+               values[4] = Int64GetDatum(stat.pages_write);
+               values[5] = Int64GetDatum(stat.pages_exists);
+               values[6] = Int64GetDatum(stat.io_error);
+               values[7] = Int64GetDatum(stat.flush);
+               values[8] = Int64GetDatum(stat.truncate);
+               values[9] = 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)
 {
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 07a86c7b7b..0d75eda8f2 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,int8,timestamptz}',
+  proargmodes => '{o,o,o,o,o,o,o,o,o,o}',
+  proargnames => 
'{name,pages_zero,pages_hit,pages_read,pages_write,pages_exists,io_error,flushes,truncates,stat_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',
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 3a65a51696..18a40e5c2d 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,
@@ -119,7 +121,8 @@ typedef struct PgStat_TableCounts
 typedef enum PgStat_Shared_Reset_Target
 {
        RESET_ARCHIVER,
-       RESET_BGWRITER
+       RESET_BGWRITER,
+       RESET_SLRU
 } PgStat_Shared_Reset_Target;
 
 /* Possible object types for resetting single counters */
@@ -422,6 +425,24 @@ 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_read;
+       PgStat_Counter m_pages_write;
+       PgStat_Counter m_pages_exists;
+       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_read;
+       PgStat_Counter pages_write;
+       PgStat_Counter pages_exists;
+       PgStat_Counter io_error;
+       PgStat_Counter flush;
+       PgStat_Counter truncate;
+       TimestampTz stat_reset_timestamp;
+} 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 */
diff --git a/src/test/regress/expected/rules.out 
b/src/test/regress/expected/rules.out
index 634f8256f7..714bce17d1 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1988,6 +1988,17 @@ 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.pages_zero,
+    s.pages_hit,
+    s.pages_read,
+    s.pages_write,
+    s.pages_exists,
+    s.io_error,
+    s.flushes,
+    s.truncates,
+    s.stat_reset
+   FROM pg_stat_get_slru() s(name, pages_zero, pages_hit, pages_read, 
pages_write, pages_exists, io_error, flushes, truncates, stat_reset);
 pg_stat_ssl| SELECT s.pid,
     s.ssl,
     s.sslversion AS version,

Reply via email to