pg.ddx.io  pgsql-hackers@postgresql.org mailing list archive  
help / color / mirror / Atom feed
From: Tomas Vondra <tomas.vondra@2ndquadrant.com>
To: pgsql-hackers@postgresql.org
Subject: SLRU statistics
Date: Sun, 19 Jan 2020 15:37:07 +0100
Message-ID: <20200119143707.gyinppnigokesjok@development> (raw)

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 */

Attachments:

  [text/plain] stats-slru-v1.patch (24.7K, ../20200119143707.gyinppnigokesjok@development/2-stats-slru-v1.patch)
  download | inline diff:
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 */

view thread (49+ messages)  latest in thread

Message-ID: <20200119143707.gyinppnigokesjok@development>
Permalink:  ../20200119143707.gyinppnigokesjok@development/
Also on:    postgresql.org/message-id/20200119143707.gyinppnigokesjok@development

 ·  · 

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: pgsql-hackers@postgresql.org
  Cc: tomas.vondra@2ndquadrant.com
  Subject: Re: SLRU statistics
  In-Reply-To: <20200119143707.gyinppnigokesjok@development>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox