diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 13:44:03 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 13:44:03 +0000 |
commit | 293913568e6a7a86fd1479e1cff8e2ecb58d6568 (patch) | |
tree | fc3b469a3ec5ab71b36ea97cc7aaddb838423a0c /contrib/pg_buffercache | |
parent | Initial commit. (diff) | |
download | postgresql-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/.gitignore | 4 | ||||
-rw-r--r-- | contrib/pg_buffercache/Makefile | 25 | ||||
-rw-r--r-- | contrib/pg_buffercache/expected/pg_buffercache.out | 57 | ||||
-rw-r--r-- | contrib/pg_buffercache/meson.build | 38 | ||||
-rw-r--r-- | contrib/pg_buffercache/pg_buffercache--1.0--1.1.sql | 11 | ||||
-rw-r--r-- | contrib/pg_buffercache/pg_buffercache--1.1--1.2.sql | 6 | ||||
-rw-r--r-- | contrib/pg_buffercache/pg_buffercache--1.2--1.3.sql | 7 | ||||
-rw-r--r-- | contrib/pg_buffercache/pg_buffercache--1.2.sql | 21 | ||||
-rw-r--r-- | contrib/pg_buffercache/pg_buffercache--1.3--1.4.sql | 28 | ||||
-rw-r--r-- | contrib/pg_buffercache/pg_buffercache.control | 5 | ||||
-rw-r--r-- | contrib/pg_buffercache/pg_buffercache_pages.c | 349 | ||||
-rw-r--r-- | contrib/pg_buffercache/sql/pg_buffercache.sql | 28 |
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(); |