Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion contrib/pg_buffercache/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ 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 pg_buffercache--1.4--1.5.sql \
pg_buffercache--1.5--1.6.sql pg_buffercache--1.6--1.7.sql
pg_buffercache--1.5--1.6.sql pg_buffercache--1.6--1.7.sql \
pg_buffercache--1.7--1.8.sql
PGFILEDESC = "pg_buffercache - monitoring of shared buffer cache in real-time"

REGRESS = pg_buffercache pg_buffercache_numa
Expand Down
14 changes: 14 additions & 0 deletions contrib/pg_buffercache/expected/pg_buffercache.out
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ SELECT count(*) > 0 FROM pg_buffercache_usage_counts() WHERE buffers >= 0;
t
(1 row)

SELECT count(*) > 0 FROM pg_buffercache_relation_stats() 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;
Expand All @@ -46,6 +52,8 @@ 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
SELECT * FROM pg_buffercache_relation_stats();
ERROR: permission denied for function pg_buffercache_relation_stats
RESET role;
-- Check that pg_monitor is allowed to query view / function
SET ROLE pg_monitor;
Expand Down Expand Up @@ -73,6 +81,12 @@ SELECT count(*) > 0 FROM pg_buffercache_usage_counts();
t
(1 row)

SELECT count(*) > 0 FROM pg_buffercache_relation_stats();
?column?
----------
t
(1 row)

RESET role;
------
---- Test pg_buffercache_evict* and pg_buffercache_mark_dirty* functions
Expand Down
1 change: 1 addition & 0 deletions contrib/pg_buffercache/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ install_data(
'pg_buffercache--1.4--1.5.sql',
'pg_buffercache--1.5--1.6.sql',
'pg_buffercache--1.6--1.7.sql',
'pg_buffercache--1.7--1.8.sql',
'pg_buffercache.control',
kwargs: contrib_data_args,
)
Expand Down
20 changes: 20 additions & 0 deletions contrib/pg_buffercache/pg_buffercache--1.7--1.8.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/* contrib/pg_buffercache/pg_buffercache--1.7--1.8.sql */

-- complain if script is sourced in psql, rather than via ALTER EXTENSION
\echo Use "ALTER EXTENSION pg_buffercache UPDATE TO '1.8'" to load this file. \quit

CREATE FUNCTION pg_buffercache_relation_stats(
OUT relfilenode oid,
OUT reltablespace oid,
OUT reldatabase oid,
OUT relforknumber int2,
OUT buffers int4,
OUT buffers_dirty int4,
OUT buffers_pinned int4,
OUT usagecount_avg float8)
RETURNS SETOF record
AS 'MODULE_PATHNAME', 'pg_buffercache_relation_stats'
LANGUAGE C PARALLEL SAFE;

REVOKE ALL ON FUNCTION pg_buffercache_relation_stats() FROM PUBLIC;
GRANT EXECUTE ON FUNCTION pg_buffercache_relation_stats() TO pg_monitor;
2 changes: 1 addition & 1 deletion contrib/pg_buffercache/pg_buffercache.control
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# pg_buffercache extension
comment = 'examine the shared buffer cache'
default_version = '1.7'
default_version = '1.8'
module_pathname = '$libdir/pg_buffercache'
relocatable = true
134 changes: 134 additions & 0 deletions contrib/pg_buffercache/pg_buffercache_pages.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@
#include "port/pg_numa.h"
#include "storage/buf_internals.h"
#include "storage/bufmgr.h"
#include "utils/hsearch.h"
#include "utils/rel.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
#define NUM_BUFFERCACHE_RELATION_STATS_ELEM 8
#define NUM_BUFFERCACHE_EVICT_ELEM 2
#define NUM_BUFFERCACHE_EVICT_RELATION_ELEM 3
#define NUM_BUFFERCACHE_EVICT_ALL_ELEM 3
Expand Down Expand Up @@ -107,8 +109,33 @@ PG_FUNCTION_INFO_V1(pg_buffercache_evict_all);
PG_FUNCTION_INFO_V1(pg_buffercache_mark_dirty);
PG_FUNCTION_INFO_V1(pg_buffercache_mark_dirty_relation);
PG_FUNCTION_INFO_V1(pg_buffercache_mark_dirty_all);
PG_FUNCTION_INFO_V1(pg_buffercache_relation_stats);


/*
* Hash key for pg_buffercache_relation_stats — groups by relation identity.
*/
typedef struct
{
RelFileNumber relfilenumber;
Oid reltablespace;
Oid reldatabase;
ForkNumber forknum;
} BufferRelStatsKey;

/*
* Hash entry for pg_buffercache_relation_stats — accumulates per-relation
* buffer statistics.
*/
typedef struct
{
BufferRelStatsKey key; /* must be first */
int32 buffers;
int32 buffers_dirty;
int32 buffers_pinned;
int64 usagecount_total;
} BufferRelStatsEntry;

/* Only need to touch memory once per backend process lifetime */
static bool firstNumaTouch = true;

Expand Down Expand Up @@ -958,3 +985,110 @@ pg_buffercache_mark_dirty_all(PG_FUNCTION_ARGS)

PG_RETURN_DATUM(result);
}

/*
* pg_buffercache_relation_stats
*
* Produces a set of rows that summarize buffer cache usage per relation-fork
* combination. This enables monitoring scripts to only get the summary stats,
* instead of accumulating in a query with the full buffer information.
*/
Datum
pg_buffercache_relation_stats(PG_FUNCTION_ARGS)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
HTAB *relstats_hash;
HASHCTL hash_ctl;
HASH_SEQ_STATUS hash_seq;
BufferRelStatsEntry *entry;
Datum values[NUM_BUFFERCACHE_RELATION_STATS_ELEM];
bool nulls[NUM_BUFFERCACHE_RELATION_STATS_ELEM] = {0};

InitMaterializedSRF(fcinfo, 0);

/* Create a hash table to aggregate stats by relation-fork */
hash_ctl.keysize = sizeof(BufferRelStatsKey);
hash_ctl.entrysize = sizeof(BufferRelStatsEntry);
hash_ctl.hcxt = CurrentMemoryContext;

relstats_hash = hash_create("pg_buffercache relation stats",
128,
&hash_ctl,
HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);

/* Single pass over all buffers */
for (int i = 0; i < NBuffers; i++)
{
BufferDesc *bufHdr;
uint64 buf_state;
BufferRelStatsKey key;
bool found;

CHECK_FOR_INTERRUPTS();

/*
* Read buffer state without locking, same as pg_buffercache_summary
* and pg_buffercache_usage_counts. Locking wouldn't provide a
* meaningfully more consistent result since buffers can change state
* immediately after we release the lock.
*/
bufHdr = GetBufferDescriptor(i);
buf_state = pg_atomic_read_u64(&bufHdr->state);

/* Skip unused/invalid buffers */
if (!(buf_state & BM_VALID))
continue;

key.relfilenumber = BufTagGetRelNumber(&bufHdr->tag);
key.reltablespace = bufHdr->tag.spcOid;
key.reldatabase = bufHdr->tag.dbOid;
key.forknum = BufTagGetForkNum(&bufHdr->tag);

entry = (BufferRelStatsEntry *) hash_search(relstats_hash,
&key,
HASH_ENTER,
&found);

if (!found)
{
entry->buffers = 0;
entry->buffers_dirty = 0;
entry->buffers_pinned = 0;
entry->usagecount_total = 0;
}

entry->buffers++;
entry->usagecount_total += BUF_STATE_GET_USAGECOUNT(buf_state);

if (buf_state & BM_DIRTY)
entry->buffers_dirty++;

if (BUF_STATE_GET_REFCOUNT(buf_state) > 0)
entry->buffers_pinned++;
}

/* Emit one row per hash entry */
hash_seq_init(&hash_seq, relstats_hash);
while ((entry = (BufferRelStatsEntry *) hash_seq_search(&hash_seq)) != NULL)
{
if (entry->buffers == 0)
continue;

values[0] = ObjectIdGetDatum(entry->key.relfilenumber);
values[1] = ObjectIdGetDatum(entry->key.reltablespace);
values[2] = ObjectIdGetDatum(entry->key.reldatabase);
values[3] = Int16GetDatum(entry->key.forknum);
values[4] = Int32GetDatum(entry->buffers);
values[5] = Int32GetDatum(entry->buffers_dirty);
values[6] = Int32GetDatum(entry->buffers_pinned);
values[7] = Float8GetDatum((double) entry->usagecount_total /
entry->buffers);

tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc,
values, nulls);
}

hash_destroy(relstats_hash);

return (Datum) 0;
}
4 changes: 4 additions & 0 deletions contrib/pg_buffercache/sql/pg_buffercache.sql
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ from pg_buffercache_summary();

SELECT count(*) > 0 FROM pg_buffercache_usage_counts() WHERE buffers >= 0;

SELECT count(*) > 0 FROM pg_buffercache_relation_stats() 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;
Expand All @@ -26,6 +28,7 @@ SELECT * FROM pg_buffercache_os_pages;
SELECT * FROM pg_buffercache_pages() AS p (wrong int);
SELECT * FROM pg_buffercache_summary();
SELECT * FROM pg_buffercache_usage_counts();
SELECT * FROM pg_buffercache_relation_stats();
RESET role;

-- Check that pg_monitor is allowed to query view / function
Expand All @@ -34,6 +37,7 @@ SELECT count(*) > 0 FROM pg_buffercache;
SELECT count(*) > 0 FROM pg_buffercache_os_pages;
SELECT buffers_used + buffers_unused > 0 FROM pg_buffercache_summary();
SELECT count(*) > 0 FROM pg_buffercache_usage_counts();
SELECT count(*) > 0 FROM pg_buffercache_relation_stats();
RESET role;


Expand Down
Loading