stats-slru-v2.patch

text/plain

Filename: stats-slru-v2.patch
Type: text/plain
Part: 0
Message: Re: SLRU statistics

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: unified
Series: patch v2
File+
src/backend/access/transam/clog.c 2 1
src/backend/access/transam/commit_ts.c 1 1
src/backend/access/transam/multixact.c 2 2
src/backend/access/transam/slru.c 29 1
src/backend/access/transam/subtrans.c 1 1
src/backend/catalog/system_views.sql 14 0
src/backend/commands/async.c 2 1
src/backend/postmaster/pgstat.c 199 0
src/backend/storage/lmgr/predicate.c 1 1
src/backend/utils/adt/pgstatfuncs.c 90 0
src/include/access/slru.h 17 1
src/include/catalog/pg_proc.dat 10 0
src/include/pgstat.h 55 1
src/test/regress/expected/rules.out 11 0
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,