diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 55694c4368..85ac79f07e 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -8362,6 +8362,11 @@ SCRAM-SHA-256$<iteration count>:&l planner statistics + + pg_shmem_allocations + shared memory allocations + + pg_stats_ext extended planner statistics @@ -10748,6 +10753,86 @@ SELECT * FROM pg_locks pl LEFT JOIN pg_prepared_xacts ppx + + <structname>pg_shmem_allocations</structname> + + + pg_shmem_allocations + + + + The pg_shmem_allocations view shows allocations + made from the server's main shared memory segment. This includes both + memory allocated by postgres itself and memory + allocated by extensions using the mechanisms detailed in + . + + + + Note that this view does not include memory allocated using the dynamic + shared memory infrastructure. + + + + <structname>pg_shmem_allocations</structname> Columns + + + + + Name + Type + Description + + + + + + name + text + The name of the shared memory allocation. NULL for unused memory + and <anonymous> for anonymous + allocations. + + + + off + bigint + The offset at which the allocation starts. NULL for anonymous + allocations and unused memory. + + + + size + bigint + Size of the allocation + + + + allocated_size + bigint + Size of the allocation including padding. For anonymous + allocations, no information about padding is available, so the + size and allocated_size columns + will always be equal. Padding is not meaningful for free memory, so + the columns will be equal in that case also. + + + +
+ + + Anonymous allocations are allocations that have been made + with ShmemAlloc() directly, rather than via + ShmemInitStruct() or + ShmemInitHash(). + + + + By default, the pg_shmem_allocations view can be + read only by superusers. + +
+ <structname>pg_stats</structname> diff --git a/doc/src/sgml/xfunc.sgml b/doc/src/sgml/xfunc.sgml index 5616524cfd..ca5e6efd7e 100644 --- a/doc/src/sgml/xfunc.sgml +++ b/doc/src/sgml/xfunc.sgml @@ -3239,7 +3239,7 @@ CREATE FUNCTION make_array(anyelement) RETURNS anyarray - + Shared Memory and LWLocks diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 2fc3e3ff90..773edf85e7 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -547,6 +547,12 @@ CREATE VIEW pg_config AS REVOKE ALL on pg_config FROM PUBLIC; REVOKE EXECUTE ON FUNCTION pg_config() FROM PUBLIC; +CREATE VIEW pg_shmem_allocations AS + SELECT * FROM pg_get_shmem_allocations(); + +REVOKE ALL ON pg_shmem_allocations FROM PUBLIC; +REVOKE EXECUTE ON FUNCTION pg_get_shmem_allocations() FROM PUBLIC; + -- Statistics views CREATE VIEW pg_stat_all_tables AS diff --git a/src/backend/storage/ipc/shmem.c b/src/backend/storage/ipc/shmem.c index a4f0ec1a67..2892a573e4 100644 --- a/src/backend/storage/ipc/shmem.c +++ b/src/backend/storage/ipc/shmem.c @@ -66,12 +66,16 @@ #include "postgres.h" #include "access/transam.h" +#include "fmgr.h" +#include "funcapi.h" #include "miscadmin.h" #include "storage/lwlock.h" #include "storage/pg_shmem.h" #include "storage/shmem.h" #include "storage/spin.h" +#include "utils/builtins.h" +static void *ShmemAllocRaw(Size size, Size *allocated_size); /* shared memory global variables */ @@ -157,8 +161,9 @@ void * ShmemAlloc(Size size) { void *newSpace; + Size allocated_size; - newSpace = ShmemAllocNoError(size); + newSpace = ShmemAllocRaw(size, &allocated_size); if (!newSpace) ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), @@ -174,6 +179,20 @@ ShmemAlloc(Size size) */ void * ShmemAllocNoError(Size size) +{ + Size allocated_size; + + return ShmemAllocRaw(size, &allocated_size); +} + +/* + * ShmemAllocRaw -- allocate align chunk and return allocated size + * + * Also sets *allocated_size to the number of bytes allocated, which will + * be equal to the number requested plus any padding we choose to add. + */ +static void * +ShmemAllocRaw(Size size, Size *allocated_size) { Size newStart; Size newFree; @@ -191,6 +210,7 @@ ShmemAllocNoError(Size size) * won't be sufficient. */ size = CACHELINEALIGN(size); + *allocated_size = size; Assert(ShmemSegHdr != NULL); @@ -441,8 +461,10 @@ ShmemInitStruct(const char *name, Size size, bool *foundPtr) } else { + Size allocated_size; + /* It isn't in the table yet. allocate and initialize it */ - structPtr = ShmemAllocNoError(size); + structPtr = ShmemAllocRaw(size, &allocated_size); if (structPtr == NULL) { /* out of memory; remove the failed ShmemIndex entry */ @@ -455,6 +477,7 @@ ShmemInitStruct(const char *name, Size size, bool *foundPtr) name, size))); } result->size = size; + result->allocated_size = allocated_size; result->location = structPtr; } @@ -503,3 +526,82 @@ mul_size(Size s1, Size s2) errmsg("requested shared memory size overflows size_t"))); return result; } + +/* SQL SRF showing allocated shared memory */ +Datum +pg_get_shmem_allocations(PG_FUNCTION_ARGS) +{ +#define PG_GET_SHMEM_SIZES_COLS 4 + ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; + TupleDesc tupdesc; + Tuplestorestate *tupstore; + MemoryContext per_query_ctx; + MemoryContext oldcontext; + HASH_SEQ_STATUS hstat; + ShmemIndexEnt *ent; + Size named_allocated = 0; + Datum values[PG_GET_SHMEM_SIZES_COLS]; + bool nulls[PG_GET_SHMEM_SIZES_COLS]; + + /* 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); + + LWLockAcquire(ShmemIndexLock, LW_SHARED); + + hash_seq_init(&hstat, ShmemIndex); + + /* output all allocated entries */ + memset(nulls, 0, sizeof(nulls)); + while ((ent = (ShmemIndexEnt *) hash_seq_search(&hstat)) != NULL) + { + values[0] = CStringGetTextDatum(ent->key); + values[1] = Int64GetDatum((char *) ent->location - (char *) ShmemSegHdr); + values[2] = Int64GetDatum(ent->size); + values[3] = Int64GetDatum(ent->allocated_size); + named_allocated += ent->allocated_size; + + tuplestore_putvalues(tupstore, tupdesc, values, nulls); + } + + /* output shared memory allocated but not counted via the shmem index */ + values[0] = CStringGetTextDatum(""); + nulls[1] = true; + values[2] = Int64GetDatum(ShmemSegHdr->freeoffset - named_allocated); + values[3] = values[2]; + tuplestore_putvalues(tupstore, tupdesc, values, nulls); + + /* output as-of-yet unused shared memory */ + nulls[0] = true; + values[1] = Int64GetDatum(ShmemSegHdr->freeoffset); + nulls[1] = false; + values[2] = Int64GetDatum(ShmemSegHdr->totalsize - ShmemSegHdr->freeoffset); + values[3] = values[2]; + tuplestore_putvalues(tupstore, tupdesc, values, nulls); + + LWLockRelease(ShmemIndexLock); + + tuplestore_donestoring(tupstore); + + return (Datum) 0; +} diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 59f1ff01ab..427faa3c3b 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -7663,6 +7663,15 @@ proparallel => 'r', prorettype => 'float8', proargtypes => '', prosrc => 'pg_notification_queue_usage' }, +# shared memory usage +{ oid => '8613', + descr => 'allocations from the main shared memory segment', + proname => 'pg_get_shmem_allocations', 'prorows' => 50, 'proretset' => 't', + provolatile => 'v', 'prorettype' => 'record', 'proargtypes' => '', + proallargtypes => '{text,int8,int8,int8}', proargmodes => '{o,o,o,o}', + proargnames => '{name,off,size,allocated_size}', + prosrc => 'pg_get_shmem_allocations' }, + # non-persistent series generator { oid => '1066', descr => 'non-persistent series generator', proname => 'generate_series', prorows => '1000', diff --git a/src/include/storage/shmem.h b/src/include/storage/shmem.h index 243b73f8bb..0c1af89206 100644 --- a/src/include/storage/shmem.h +++ b/src/include/storage/shmem.h @@ -59,7 +59,8 @@ typedef struct { char key[SHMEM_INDEX_KEYSIZE]; /* string name */ void *location; /* location in shared mem */ - Size size; /* # bytes allocated for the structure */ + Size size; /* # bytes requested for the structure */ + Size allocated_size; /* # bytes actually allocated */ } ShmemIndexEnt; /* diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 80a07825b9..62eaf90a0f 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1721,6 +1721,11 @@ pg_shadow| SELECT pg_authid.rolname AS usename, FROM (pg_authid LEFT JOIN pg_db_role_setting s ON (((pg_authid.oid = s.setrole) AND (s.setdatabase = (0)::oid)))) WHERE pg_authid.rolcanlogin; +pg_shmem_allocations| SELECT pg_get_shmem_allocations.name, + pg_get_shmem_allocations.off, + pg_get_shmem_allocations.size, + pg_get_shmem_allocations.allocated_size + FROM pg_get_shmem_allocations() pg_get_shmem_allocations(name, off, size, allocated_size); pg_stat_activity| SELECT s.datid, d.datname, s.pid,