From f57791985ac3d776cb67e9d69befde538cfdf13b Mon Sep 17 00:00:00 2001
From: Andres Freund <andres@anarazel.de>
Date: Fri, 22 Aug 2014 00:28:37 +0200
Subject: [PATCH] Add pinning_backends column to the pg_buffercache extension.

The new column shows how many backends have a buffer pinned. That can
be useful during development or to diagnose production issues
e.g. caused by vacuum waiting for cleanup locks.

To handle upgrades transparently - the extension might be used in
views - deal with callers expecting the old number of columns.

Reviewed by Fujii Masao and Rajeev rastogi.
---
 contrib/pg_buffercache/Makefile               |  2 +-
 .../pg_buffercache--1.0--1.1.sql              | 11 ++++++
 ...cache--1.0.sql => pg_buffercache--1.1.sql} |  5 ++-
 contrib/pg_buffercache/pg_buffercache.control |  2 +-
 contrib/pg_buffercache/pg_buffercache_pages.c | 37 ++++++++++++++++++-
 doc/src/sgml/pgbuffercache.sgml               |  7 ++++
 6 files changed, 58 insertions(+), 6 deletions(-)
 create mode 100644 contrib/pg_buffercache/pg_buffercache--1.0--1.1.sql
 rename contrib/pg_buffercache/{pg_buffercache--1.0.sql => pg_buffercache--1.1.sql} (88%)

diff --git a/contrib/pg_buffercache/Makefile b/contrib/pg_buffercache/Makefile
index c5297d98d48..065d3d690a9 100644
--- a/contrib/pg_buffercache/Makefile
+++ b/contrib/pg_buffercache/Makefile
@@ -4,7 +4,7 @@ MODULE_big = pg_buffercache
 OBJS = pg_buffercache_pages.o $(WIN32RES)
 
 EXTENSION = pg_buffercache
-DATA = pg_buffercache--1.0.sql pg_buffercache--unpackaged--1.0.sql
+DATA = pg_buffercache--1.1.sql pg_buffercache--1.0--1.1.sql pg_buffercache--unpackaged--1.0.sql
 PGFILEDESC = "pg_buffercache - monitoring of shared buffer cache in real-time"
 
 ifdef USE_PGXS
diff --git a/contrib/pg_buffercache/pg_buffercache--1.0--1.1.sql b/contrib/pg_buffercache/pg_buffercache--1.0--1.1.sql
new file mode 100644
index 00000000000..54d02f58c01
--- /dev/null
+++ b/contrib/pg_buffercache/pg_buffercache--1.0--1.1.sql
@@ -0,0 +1,11 @@
+/* contrib/pg_buffercache/pg_buffercache--1.0--1.1.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION pg_buffercache UPDATE TO '1.1'" to load this file. \quit
+
+-- Upgrade view to 1.1. format
+CREATE OR REPLACE VIEW pg_buffercache AS
+	SELECT P.* FROM pg_buffercache_pages() AS P
+	(bufferid integer, relfilenode oid, reltablespace oid, reldatabase oid,
+	 relforknumber int2, relblocknumber int8, isdirty bool, usagecount int2,
+	 pinning_backends int4);
diff --git a/contrib/pg_buffercache/pg_buffercache--1.0.sql b/contrib/pg_buffercache/pg_buffercache--1.1.sql
similarity index 88%
rename from contrib/pg_buffercache/pg_buffercache--1.0.sql
rename to contrib/pg_buffercache/pg_buffercache--1.1.sql
index 4ca4c44256c..f3b6482fa62 100644
--- a/contrib/pg_buffercache/pg_buffercache--1.0.sql
+++ b/contrib/pg_buffercache/pg_buffercache--1.1.sql
@@ -1,4 +1,4 @@
-/* contrib/pg_buffercache/pg_buffercache--1.0.sql */
+/* contrib/pg_buffercache/pg_buffercache--1.1.sql */
 
 -- complain if script is sourced in psql, rather than via CREATE EXTENSION
 \echo Use "CREATE EXTENSION pg_buffercache" to load this file. \quit
@@ -13,7 +13,8 @@ LANGUAGE C;
 CREATE VIEW pg_buffercache AS
 	SELECT P.* FROM pg_buffercache_pages() AS P
 	(bufferid integer, relfilenode oid, reltablespace oid, reldatabase oid,
-	 relforknumber int2, relblocknumber int8, isdirty bool, usagecount int2);
+	 relforknumber int2, relblocknumber int8, isdirty bool, usagecount int2,
+	 pinning_backends int4);
 
 -- Don't want these to be available to public.
 REVOKE ALL ON FUNCTION pg_buffercache_pages() FROM PUBLIC;
diff --git a/contrib/pg_buffercache/pg_buffercache.control b/contrib/pg_buffercache/pg_buffercache.control
index 709513c334e..5494e2fae52 100644
--- a/contrib/pg_buffercache/pg_buffercache.control
+++ b/contrib/pg_buffercache/pg_buffercache.control
@@ -1,5 +1,5 @@
 # pg_buffercache extension
 comment = 'examine the shared buffer cache'
-default_version = '1.0'
+default_version = '1.1'
 module_pathname = '$libdir/pg_buffercache'
 relocatable = true
diff --git a/contrib/pg_buffercache/pg_buffercache_pages.c b/contrib/pg_buffercache/pg_buffercache_pages.c
index b205683bffc..d3b1ba3245d 100644
--- a/contrib/pg_buffercache/pg_buffercache_pages.c
+++ b/contrib/pg_buffercache/pg_buffercache_pages.c
@@ -15,7 +15,8 @@
 #include "storage/bufmgr.h"
 
 
-#define NUM_BUFFERCACHE_PAGES_ELEM	8
+#define NUM_BUFFERCACHE_PAGES_MIN_ELEM	8
+#define NUM_BUFFERCACHE_PAGES_ELEM	9
 
 PG_MODULE_MAGIC;
 
@@ -33,6 +34,12 @@ typedef struct
 	bool		isvalid;
 	bool		isdirty;
 	uint16		usagecount;
+	/*
+	 * An int32 is sufficiently large, as MAX_BACKENDS prevents a buffer from
+	 * being pinned by too many backends and each backend will only pin once
+	 * because of bufmgr.c's PrivateRefCount array.
+	 */
+	int32		pinning_backends;
 } BufferCachePagesRec;
 
 
@@ -60,6 +67,7 @@ pg_buffercache_pages(PG_FUNCTION_ARGS)
 	MemoryContext oldcontext;
 	BufferCachePagesContext *fctx;		/* User function context. */
 	TupleDesc	tupledesc;
+	TupleDesc	expected_tupledesc;
 	HeapTuple	tuple;
 
 	if (SRF_IS_FIRSTCALL())
@@ -75,8 +83,23 @@ pg_buffercache_pages(PG_FUNCTION_ARGS)
 		/* Create a user function context for cross-call persistence */
 		fctx = (BufferCachePagesContext *) palloc(sizeof(BufferCachePagesContext));
 
+		/*
+		 * To smoothly support upgrades from version 1.0 of this extension
+		 * transparently handle the (non-)existance of the pinning_backends
+		 * column. We unfortunately have to get the result type for that... -
+		 * we can't use the result type determined by the function definition
+		 * without potentially crashing when somebody uses the old (or even
+		 * wrong) function definition though.
+		 */
+		if (get_call_result_type(fcinfo, NULL, &expected_tupledesc) != TYPEFUNC_COMPOSITE)
+			elog(ERROR, "return type must be a row type");
+
+		if (expected_tupledesc->natts < NUM_BUFFERCACHE_PAGES_MIN_ELEM ||
+			expected_tupledesc->natts > NUM_BUFFERCACHE_PAGES_ELEM)
+			elog(ERROR, "incorrect number of output arguments");
+
 		/* Construct a tuple descriptor for the result rows. */
-		tupledesc = CreateTemplateTupleDesc(NUM_BUFFERCACHE_PAGES_ELEM, false);
+		tupledesc = CreateTemplateTupleDesc(expected_tupledesc->natts, false);
 		TupleDescInitEntry(tupledesc, (AttrNumber) 1, "bufferid",
 						   INT4OID, -1, 0);
 		TupleDescInitEntry(tupledesc, (AttrNumber) 2, "relfilenode",
@@ -94,6 +117,10 @@ pg_buffercache_pages(PG_FUNCTION_ARGS)
 		TupleDescInitEntry(tupledesc, (AttrNumber) 8, "usage_count",
 						   INT2OID, -1, 0);
 
+		if (expected_tupledesc->natts == NUM_BUFFERCACHE_PAGES_ELEM)
+			TupleDescInitEntry(tupledesc, (AttrNumber) 9, "pinning_backends",
+							   INT4OID, -1, 0);
+
 		fctx->tupdesc = BlessTupleDesc(tupledesc);
 
 		/* Allocate NBuffers worth of BufferCachePagesRec records. */
@@ -131,6 +158,7 @@ pg_buffercache_pages(PG_FUNCTION_ARGS)
 			fctx->record[i].forknum = bufHdr->tag.forkNum;
 			fctx->record[i].blocknum = bufHdr->tag.blockNum;
 			fctx->record[i].usagecount = bufHdr->usage_count;
+			fctx->record[i].pinning_backends = bufHdr->refcount;
 
 			if (bufHdr->flags & BM_DIRTY)
 				fctx->record[i].isdirty = true;
@@ -185,6 +213,8 @@ pg_buffercache_pages(PG_FUNCTION_ARGS)
 			nulls[5] = true;
 			nulls[6] = true;
 			nulls[7] = true;
+			/* unused for v1.0 callers, but the array is always long enough */
+			nulls[8] = true;
 		}
 		else
 		{
@@ -202,6 +232,9 @@ pg_buffercache_pages(PG_FUNCTION_ARGS)
 			nulls[6] = false;
 			values[7] = Int16GetDatum(fctx->record[i].usagecount);
 			nulls[7] = false;
+			/* unused for v1.0 callers, but the array is always long enough */
+			values[8] = Int32GetDatum(fctx->record[i].pinning_backends);
+			nulls[8] = false;
 		}
 
 		/* Build and return the tuple. */
diff --git a/doc/src/sgml/pgbuffercache.sgml b/doc/src/sgml/pgbuffercache.sgml
index 4eb02c06239..f379be225f0 100644
--- a/doc/src/sgml/pgbuffercache.sgml
+++ b/doc/src/sgml/pgbuffercache.sgml
@@ -106,6 +106,13 @@
       <entry>Clock-sweep access count</entry>
      </row>
 
+     <row>
+      <entry><structfield>pinning_backends</structfield></entry>
+      <entry><type>integer</type></entry>
+      <entry></entry>
+      <entry>Number of backends pinning this buffer</entry>
+     </row>
+
     </tbody>
    </tgroup>
   </table>
-- 
GitLab