summaryrefslogtreecommitdiffstats
path: root/contrib/pg_buffercache
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 13:44:03 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 13:44:03 +0000
commit293913568e6a7a86fd1479e1cff8e2ecb58d6568 (patch)
treefc3b469a3ec5ab71b36ea97cc7aaddb838423a0c /contrib/pg_buffercache
parentInitial commit. (diff)
downloadpostgresql-16-293913568e6a7a86fd1479e1cff8e2ecb58d6568.tar.xz
postgresql-16-293913568e6a7a86fd1479e1cff8e2ecb58d6568.zip
Adding upstream version 16.2.upstream/16.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'contrib/pg_buffercache')
-rw-r--r--contrib/pg_buffercache/.gitignore4
-rw-r--r--contrib/pg_buffercache/Makefile25
-rw-r--r--contrib/pg_buffercache/expected/pg_buffercache.out57
-rw-r--r--contrib/pg_buffercache/meson.build38
-rw-r--r--contrib/pg_buffercache/pg_buffercache--1.0--1.1.sql11
-rw-r--r--contrib/pg_buffercache/pg_buffercache--1.1--1.2.sql6
-rw-r--r--contrib/pg_buffercache/pg_buffercache--1.2--1.3.sql7
-rw-r--r--contrib/pg_buffercache/pg_buffercache--1.2.sql21
-rw-r--r--contrib/pg_buffercache/pg_buffercache--1.3--1.4.sql28
-rw-r--r--contrib/pg_buffercache/pg_buffercache.control5
-rw-r--r--contrib/pg_buffercache/pg_buffercache_pages.c349
-rw-r--r--contrib/pg_buffercache/sql/pg_buffercache.sql28
12 files changed, 579 insertions, 0 deletions
diff --git a/contrib/pg_buffercache/.gitignore b/contrib/pg_buffercache/.gitignore
new file mode 100644
index 0000000..5dcb3ff
--- /dev/null
+++ b/contrib/pg_buffercache/.gitignore
@@ -0,0 +1,4 @@
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/contrib/pg_buffercache/Makefile b/contrib/pg_buffercache/Makefile
new file mode 100644
index 0000000..d6b58d4
--- /dev/null
+++ b/contrib/pg_buffercache/Makefile
@@ -0,0 +1,25 @@
+# contrib/pg_buffercache/Makefile
+
+MODULE_big = pg_buffercache
+OBJS = \
+ $(WIN32RES) \
+ pg_buffercache_pages.o
+
+EXTENSION = pg_buffercache
+DATA = pg_buffercache--1.2.sql pg_buffercache--1.2--1.3.sql \
+ pg_buffercache--1.1--1.2.sql pg_buffercache--1.0--1.1.sql \
+ pg_buffercache--1.3--1.4.sql
+PGFILEDESC = "pg_buffercache - monitoring of shared buffer cache in real-time"
+
+REGRESS = pg_buffercache
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS := $(shell $(PG_CONFIG) --pgxs)
+include $(PGXS)
+else
+subdir = contrib/pg_buffercache
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/pg_buffercache/expected/pg_buffercache.out b/contrib/pg_buffercache/expected/pg_buffercache.out
new file mode 100644
index 0000000..b745dc6
--- /dev/null
+++ b/contrib/pg_buffercache/expected/pg_buffercache.out
@@ -0,0 +1,57 @@
+CREATE EXTENSION pg_buffercache;
+select count(*) = (select setting::bigint
+ from pg_settings
+ where name = 'shared_buffers')
+from pg_buffercache;
+ ?column?
+----------
+ t
+(1 row)
+
+select buffers_used + buffers_unused > 0,
+ buffers_dirty <= buffers_used,
+ buffers_pinned <= buffers_used
+from pg_buffercache_summary();
+ ?column? | ?column? | ?column?
+----------+----------+----------
+ t | t | t
+(1 row)
+
+SELECT count(*) > 0 FROM pg_buffercache_usage_counts() WHERE buffers >= 0;
+ ?column?
+----------
+ t
+(1 row)
+
+-- Check that the functions / views can't be accessed by default. To avoid
+-- having to create a dedicated user, use the pg_database_owner pseudo-role.
+SET ROLE pg_database_owner;
+SELECT * FROM pg_buffercache;
+ERROR: permission denied for view pg_buffercache
+SELECT * FROM pg_buffercache_pages() AS p (wrong int);
+ERROR: permission denied for function pg_buffercache_pages
+SELECT * FROM pg_buffercache_summary();
+ERROR: permission denied for function pg_buffercache_summary
+SELECT * FROM pg_buffercache_usage_counts();
+ERROR: permission denied for function pg_buffercache_usage_counts
+RESET role;
+-- Check that pg_monitor is allowed to query view / function
+SET ROLE pg_monitor;
+SELECT count(*) > 0 FROM pg_buffercache;
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT buffers_used + buffers_unused > 0 FROM pg_buffercache_summary();
+ ?column?
+----------
+ t
+(1 row)
+
+SELECT count(*) > 0 FROM pg_buffercache_usage_counts();
+ ?column?
+----------
+ t
+(1 row)
+
diff --git a/contrib/pg_buffercache/meson.build b/contrib/pg_buffercache/meson.build
new file mode 100644
index 0000000..c51edf3
--- /dev/null
+++ b/contrib/pg_buffercache/meson.build
@@ -0,0 +1,38 @@
+# Copyright (c) 2022-2023, PostgreSQL Global Development Group
+
+pg_buffercache_sources = files(
+ 'pg_buffercache_pages.c',
+)
+
+if host_system == 'windows'
+ pg_buffercache_sources += rc_lib_gen.process(win32ver_rc, extra_args: [
+ '--NAME', 'pg_buffercache',
+ '--FILEDESC', 'pg_buffercache - monitoring of shared buffer cache in real-time',])
+endif
+
+pg_buffercache = shared_module('pg_buffercache',
+ pg_buffercache_sources,
+ kwargs: contrib_mod_args,
+)
+contrib_targets += pg_buffercache
+
+install_data(
+ 'pg_buffercache--1.0--1.1.sql',
+ 'pg_buffercache--1.1--1.2.sql',
+ 'pg_buffercache--1.2--1.3.sql',
+ 'pg_buffercache--1.2.sql',
+ 'pg_buffercache--1.3--1.4.sql',
+ 'pg_buffercache.control',
+ kwargs: contrib_data_args,
+)
+
+tests += {
+ 'name': 'pg_buffercache',
+ 'sd': meson.current_source_dir(),
+ 'bd': meson.current_build_dir(),
+ 'regress': {
+ 'sql': [
+ 'pg_buffercache',
+ ],
+ },
+}
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 0000000..54d02f5
--- /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.1--1.2.sql b/contrib/pg_buffercache/pg_buffercache--1.1--1.2.sql
new file mode 100644
index 0000000..5997aad
--- /dev/null
+++ b/contrib/pg_buffercache/pg_buffercache--1.1--1.2.sql
@@ -0,0 +1,6 @@
+/* contrib/pg_buffercache/pg_buffercache--1.1--1.2.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION pg_buffercache UPDATE TO '1.2'" to load this file. \quit
+
+ALTER FUNCTION pg_buffercache_pages() PARALLEL SAFE;
diff --git a/contrib/pg_buffercache/pg_buffercache--1.2--1.3.sql b/contrib/pg_buffercache/pg_buffercache--1.2--1.3.sql
new file mode 100644
index 0000000..b37ef01
--- /dev/null
+++ b/contrib/pg_buffercache/pg_buffercache--1.2--1.3.sql
@@ -0,0 +1,7 @@
+/* contrib/pg_buffercache/pg_buffercache--1.2--1.3.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION pg_buffercache UPDATE TO '1.3'" to load this file. \quit
+
+GRANT EXECUTE ON FUNCTION pg_buffercache_pages() TO pg_monitor;
+GRANT SELECT ON pg_buffercache TO pg_monitor;
diff --git a/contrib/pg_buffercache/pg_buffercache--1.2.sql b/contrib/pg_buffercache/pg_buffercache--1.2.sql
new file mode 100644
index 0000000..6ee5d84
--- /dev/null
+++ b/contrib/pg_buffercache/pg_buffercache--1.2.sql
@@ -0,0 +1,21 @@
+/* contrib/pg_buffercache/pg_buffercache--1.2.sql */
+
+-- complain if script is sourced in psql, rather than via CREATE EXTENSION
+\echo Use "CREATE EXTENSION pg_buffercache" to load this file. \quit
+
+-- Register the function.
+CREATE FUNCTION pg_buffercache_pages()
+RETURNS SETOF RECORD
+AS 'MODULE_PATHNAME', 'pg_buffercache_pages'
+LANGUAGE C PARALLEL SAFE;
+
+-- Create a view for convenient access.
+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,
+ pinning_backends int4);
+
+-- Don't want these to be available to public.
+REVOKE ALL ON FUNCTION pg_buffercache_pages() FROM PUBLIC;
+REVOKE ALL ON pg_buffercache FROM PUBLIC;
diff --git a/contrib/pg_buffercache/pg_buffercache--1.3--1.4.sql b/contrib/pg_buffercache/pg_buffercache--1.3--1.4.sql
new file mode 100644
index 0000000..d5aebf3
--- /dev/null
+++ b/contrib/pg_buffercache/pg_buffercache--1.3--1.4.sql
@@ -0,0 +1,28 @@
+/* contrib/pg_buffercache/pg_buffercache--1.3--1.4.sql */
+
+-- complain if script is sourced in psql, rather than via ALTER EXTENSION
+\echo Use "ALTER EXTENSION pg_buffercache UPDATE TO '1.4'" to load this file. \quit
+
+CREATE FUNCTION pg_buffercache_summary(
+ OUT buffers_used int4,
+ OUT buffers_unused int4,
+ OUT buffers_dirty int4,
+ OUT buffers_pinned int4,
+ OUT usagecount_avg float8)
+AS 'MODULE_PATHNAME', 'pg_buffercache_summary'
+LANGUAGE C PARALLEL SAFE;
+
+CREATE FUNCTION pg_buffercache_usage_counts(
+ OUT usage_count int4,
+ OUT buffers int4,
+ OUT dirty int4,
+ OUT pinned int4)
+RETURNS SETOF record
+AS 'MODULE_PATHNAME', 'pg_buffercache_usage_counts'
+LANGUAGE C PARALLEL SAFE;
+
+-- Don't want these to be available to public.
+REVOKE ALL ON FUNCTION pg_buffercache_summary() FROM PUBLIC;
+GRANT EXECUTE ON FUNCTION pg_buffercache_summary() TO pg_monitor;
+REVOKE ALL ON FUNCTION pg_buffercache_usage_counts() FROM PUBLIC;
+GRANT EXECUTE ON FUNCTION pg_buffercache_usage_counts() TO pg_monitor;
diff --git a/contrib/pg_buffercache/pg_buffercache.control b/contrib/pg_buffercache/pg_buffercache.control
new file mode 100644
index 0000000..a82ae5f
--- /dev/null
+++ b/contrib/pg_buffercache/pg_buffercache.control
@@ -0,0 +1,5 @@
+# pg_buffercache extension
+comment = 'examine the shared buffer cache'
+default_version = '1.4'
+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
new file mode 100644
index 0000000..3316732
--- /dev/null
+++ b/contrib/pg_buffercache/pg_buffercache_pages.c
@@ -0,0 +1,349 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_buffercache_pages.c
+ * display some contents of the buffer cache
+ *
+ * contrib/pg_buffercache/pg_buffercache_pages.c
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "catalog/pg_type.h"
+#include "funcapi.h"
+#include "storage/buf_internals.h"
+#include "storage/bufmgr.h"
+
+
+#define NUM_BUFFERCACHE_PAGES_MIN_ELEM 8
+#define NUM_BUFFERCACHE_PAGES_ELEM 9
+#define NUM_BUFFERCACHE_SUMMARY_ELEM 5
+#define NUM_BUFFERCACHE_USAGE_COUNTS_ELEM 4
+
+PG_MODULE_MAGIC;
+
+/*
+ * Record structure holding the to be exposed cache data.
+ */
+typedef struct
+{
+ uint32 bufferid;
+ RelFileNumber relfilenumber;
+ Oid reltablespace;
+ Oid reldatabase;
+ ForkNumber forknum;
+ BlockNumber blocknum;
+ 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 infrastructure.
+ */
+ int32 pinning_backends;
+} BufferCachePagesRec;
+
+
+/*
+ * Function context for data persisting over repeated calls.
+ */
+typedef struct
+{
+ TupleDesc tupdesc;
+ BufferCachePagesRec *record;
+} BufferCachePagesContext;
+
+
+/*
+ * Function returning data from the shared buffer cache - buffer number,
+ * relation node/tablespace/database/blocknum and dirty indicator.
+ */
+PG_FUNCTION_INFO_V1(pg_buffercache_pages);
+PG_FUNCTION_INFO_V1(pg_buffercache_summary);
+PG_FUNCTION_INFO_V1(pg_buffercache_usage_counts);
+
+Datum
+pg_buffercache_pages(PG_FUNCTION_ARGS)
+{
+ FuncCallContext *funcctx;
+ Datum result;
+ MemoryContext oldcontext;
+ BufferCachePagesContext *fctx; /* User function context. */
+ TupleDesc tupledesc;
+ TupleDesc expected_tupledesc;
+ HeapTuple tuple;
+
+ if (SRF_IS_FIRSTCALL())
+ {
+ int i;
+
+ funcctx = SRF_FIRSTCALL_INIT();
+
+ /* Switch context when allocating stuff to be used in later calls */
+ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+ /* 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-)existence 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(expected_tupledesc->natts);
+ TupleDescInitEntry(tupledesc, (AttrNumber) 1, "bufferid",
+ INT4OID, -1, 0);
+ TupleDescInitEntry(tupledesc, (AttrNumber) 2, "relfilenode",
+ OIDOID, -1, 0);
+ TupleDescInitEntry(tupledesc, (AttrNumber) 3, "reltablespace",
+ OIDOID, -1, 0);
+ TupleDescInitEntry(tupledesc, (AttrNumber) 4, "reldatabase",
+ OIDOID, -1, 0);
+ TupleDescInitEntry(tupledesc, (AttrNumber) 5, "relforknumber",
+ INT2OID, -1, 0);
+ TupleDescInitEntry(tupledesc, (AttrNumber) 6, "relblocknumber",
+ INT8OID, -1, 0);
+ TupleDescInitEntry(tupledesc, (AttrNumber) 7, "isdirty",
+ BOOLOID, -1, 0);
+ 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. */
+ fctx->record = (BufferCachePagesRec *)
+ MemoryContextAllocHuge(CurrentMemoryContext,
+ sizeof(BufferCachePagesRec) * NBuffers);
+
+ /* Set max calls and remember the user function context. */
+ funcctx->max_calls = NBuffers;
+ funcctx->user_fctx = fctx;
+
+ /* Return to original context when allocating transient memory */
+ MemoryContextSwitchTo(oldcontext);
+
+ /*
+ * Scan through all the buffers, saving the relevant fields in the
+ * fctx->record structure.
+ *
+ * We don't hold the partition locks, so we don't get a consistent
+ * snapshot across all buffers, but we do grab the buffer header
+ * locks, so the information of each buffer is self-consistent.
+ */
+ for (i = 0; i < NBuffers; i++)
+ {
+ BufferDesc *bufHdr;
+ uint32 buf_state;
+
+ bufHdr = GetBufferDescriptor(i);
+ /* Lock each buffer header before inspecting. */
+ buf_state = LockBufHdr(bufHdr);
+
+ fctx->record[i].bufferid = BufferDescriptorGetBuffer(bufHdr);
+ fctx->record[i].relfilenumber = BufTagGetRelNumber(&bufHdr->tag);
+ fctx->record[i].reltablespace = bufHdr->tag.spcOid;
+ fctx->record[i].reldatabase = bufHdr->tag.dbOid;
+ fctx->record[i].forknum = BufTagGetForkNum(&bufHdr->tag);
+ fctx->record[i].blocknum = bufHdr->tag.blockNum;
+ fctx->record[i].usagecount = BUF_STATE_GET_USAGECOUNT(buf_state);
+ fctx->record[i].pinning_backends = BUF_STATE_GET_REFCOUNT(buf_state);
+
+ if (buf_state & BM_DIRTY)
+ fctx->record[i].isdirty = true;
+ else
+ fctx->record[i].isdirty = false;
+
+ /* Note if the buffer is valid, and has storage created */
+ if ((buf_state & BM_VALID) && (buf_state & BM_TAG_VALID))
+ fctx->record[i].isvalid = true;
+ else
+ fctx->record[i].isvalid = false;
+
+ UnlockBufHdr(bufHdr, buf_state);
+ }
+ }
+
+ funcctx = SRF_PERCALL_SETUP();
+
+ /* Get the saved state */
+ fctx = funcctx->user_fctx;
+
+ if (funcctx->call_cntr < funcctx->max_calls)
+ {
+ uint32 i = funcctx->call_cntr;
+ Datum values[NUM_BUFFERCACHE_PAGES_ELEM];
+ bool nulls[NUM_BUFFERCACHE_PAGES_ELEM];
+
+ values[0] = Int32GetDatum(fctx->record[i].bufferid);
+ nulls[0] = false;
+
+ /*
+ * Set all fields except the bufferid to null if the buffer is unused
+ * or not valid.
+ */
+ if (fctx->record[i].blocknum == InvalidBlockNumber ||
+ fctx->record[i].isvalid == false)
+ {
+ nulls[1] = true;
+ nulls[2] = true;
+ nulls[3] = true;
+ nulls[4] = true;
+ 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
+ {
+ values[1] = ObjectIdGetDatum(fctx->record[i].relfilenumber);
+ nulls[1] = false;
+ values[2] = ObjectIdGetDatum(fctx->record[i].reltablespace);
+ nulls[2] = false;
+ values[3] = ObjectIdGetDatum(fctx->record[i].reldatabase);
+ nulls[3] = false;
+ values[4] = ObjectIdGetDatum(fctx->record[i].forknum);
+ nulls[4] = false;
+ values[5] = Int64GetDatum((int64) fctx->record[i].blocknum);
+ nulls[5] = false;
+ values[6] = BoolGetDatum(fctx->record[i].isdirty);
+ 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. */
+ tuple = heap_form_tuple(fctx->tupdesc, values, nulls);
+ result = HeapTupleGetDatum(tuple);
+
+ SRF_RETURN_NEXT(funcctx, result);
+ }
+ else
+ SRF_RETURN_DONE(funcctx);
+}
+
+Datum
+pg_buffercache_summary(PG_FUNCTION_ARGS)
+{
+ Datum result;
+ TupleDesc tupledesc;
+ HeapTuple tuple;
+ Datum values[NUM_BUFFERCACHE_SUMMARY_ELEM];
+ bool nulls[NUM_BUFFERCACHE_SUMMARY_ELEM];
+
+ int32 buffers_used = 0;
+ int32 buffers_unused = 0;
+ int32 buffers_dirty = 0;
+ int32 buffers_pinned = 0;
+ int64 usagecount_total = 0;
+
+ if (get_call_result_type(fcinfo, NULL, &tupledesc) != TYPEFUNC_COMPOSITE)
+ elog(ERROR, "return type must be a row type");
+
+ for (int i = 0; i < NBuffers; i++)
+ {
+ BufferDesc *bufHdr;
+ uint32 buf_state;
+
+ /*
+ * This function summarizes the state of all headers. Locking the
+ * buffer headers wouldn't provide an improved result as the state of
+ * the buffer can still change after we release the lock and it'd
+ * noticeably increase the cost of the function.
+ */
+ bufHdr = GetBufferDescriptor(i);
+ buf_state = pg_atomic_read_u32(&bufHdr->state);
+
+ if (buf_state & BM_VALID)
+ {
+ buffers_used++;
+ usagecount_total += BUF_STATE_GET_USAGECOUNT(buf_state);
+
+ if (buf_state & BM_DIRTY)
+ buffers_dirty++;
+ }
+ else
+ buffers_unused++;
+
+ if (BUF_STATE_GET_REFCOUNT(buf_state) > 0)
+ buffers_pinned++;
+ }
+
+ memset(nulls, 0, sizeof(nulls));
+ values[0] = Int32GetDatum(buffers_used);
+ values[1] = Int32GetDatum(buffers_unused);
+ values[2] = Int32GetDatum(buffers_dirty);
+ values[3] = Int32GetDatum(buffers_pinned);
+
+ if (buffers_used != 0)
+ values[4] = Float8GetDatum((double) usagecount_total / buffers_used);
+ else
+ nulls[4] = true;
+
+ /* Build and return the tuple. */
+ tuple = heap_form_tuple(tupledesc, values, nulls);
+ result = HeapTupleGetDatum(tuple);
+
+ PG_RETURN_DATUM(result);
+}
+
+Datum
+pg_buffercache_usage_counts(PG_FUNCTION_ARGS)
+{
+ ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
+ int usage_counts[BM_MAX_USAGE_COUNT + 1] = {0};
+ int dirty[BM_MAX_USAGE_COUNT + 1] = {0};
+ int pinned[BM_MAX_USAGE_COUNT + 1] = {0};
+ Datum values[NUM_BUFFERCACHE_USAGE_COUNTS_ELEM];
+ bool nulls[NUM_BUFFERCACHE_USAGE_COUNTS_ELEM] = {0};
+
+ InitMaterializedSRF(fcinfo, 0);
+
+ for (int i = 0; i < NBuffers; i++)
+ {
+ BufferDesc *bufHdr = GetBufferDescriptor(i);
+ uint32 buf_state = pg_atomic_read_u32(&bufHdr->state);
+ int usage_count;
+
+ usage_count = BUF_STATE_GET_USAGECOUNT(buf_state);
+ usage_counts[usage_count]++;
+
+ if (buf_state & BM_DIRTY)
+ dirty[usage_count]++;
+
+ if (BUF_STATE_GET_REFCOUNT(buf_state) > 0)
+ pinned[usage_count]++;
+ }
+
+ for (int i = 0; i < BM_MAX_USAGE_COUNT + 1; i++)
+ {
+ values[0] = Int32GetDatum(i);
+ values[1] = Int32GetDatum(usage_counts[i]);
+ values[2] = Int32GetDatum(dirty[i]);
+ values[3] = Int32GetDatum(pinned[i]);
+
+ tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls);
+ }
+
+ return (Datum) 0;
+}
diff --git a/contrib/pg_buffercache/sql/pg_buffercache.sql b/contrib/pg_buffercache/sql/pg_buffercache.sql
new file mode 100644
index 0000000..944fbb1
--- /dev/null
+++ b/contrib/pg_buffercache/sql/pg_buffercache.sql
@@ -0,0 +1,28 @@
+CREATE EXTENSION pg_buffercache;
+
+select count(*) = (select setting::bigint
+ from pg_settings
+ where name = 'shared_buffers')
+from pg_buffercache;
+
+select buffers_used + buffers_unused > 0,
+ buffers_dirty <= buffers_used,
+ buffers_pinned <= buffers_used
+from pg_buffercache_summary();
+
+SELECT count(*) > 0 FROM pg_buffercache_usage_counts() WHERE buffers >= 0;
+
+-- Check that the functions / views can't be accessed by default. To avoid
+-- having to create a dedicated user, use the pg_database_owner pseudo-role.
+SET ROLE pg_database_owner;
+SELECT * FROM pg_buffercache;
+SELECT * FROM pg_buffercache_pages() AS p (wrong int);
+SELECT * FROM pg_buffercache_summary();
+SELECT * FROM pg_buffercache_usage_counts();
+RESET role;
+
+-- Check that pg_monitor is allowed to query view / function
+SET ROLE pg_monitor;
+SELECT count(*) > 0 FROM pg_buffercache;
+SELECT buffers_used + buffers_unused > 0 FROM pg_buffercache_summary();
+SELECT count(*) > 0 FROM pg_buffercache_usage_counts();