From 834e646b0ec82d122e6518ca53128ceb4e3ad970 Mon Sep 17 00:00:00 2001 From: James Campbell Date: Fri, 4 Apr 2025 17:11:35 -0400 Subject: [PATCH] First revision --- pg_lock_hash_info--1.0.sql | 23 +++- pg_lock_hash_info.c | 217 +++++++++++++++++++++++++++++++++---- 2 files changed, 213 insertions(+), 27 deletions(-) diff --git a/pg_lock_hash_info--1.0.sql b/pg_lock_hash_info--1.0.sql index ba7d9c5..734eaaf 100644 --- a/pg_lock_hash_info--1.0.sql +++ b/pg_lock_hash_info--1.0.sql @@ -1,8 +1,23 @@ CREATE FUNCTION pg_lock_hash_info() RETURNS TABLE ( - hash_type CHAR, + dsize INT, + nsegs INT, + max_bucket INT, + keysize INT, + entrysize INT, + num_partitions INT, + max_dsize INT, + ssize INT, + sshift INT, + nelem_alloc INT +) AS 'pg_lock_hash_info', 'pg_lock_hash_info' +LANGUAGE C STRICT; + +CREATE FUNCTION pg_lock_hash_bucket_info() +RETURNS TABLE ( directory INT, + bucket INT, bucket_hash INT, - entries INT, - free_slots INT -) AS 'pg_lock_hash_info' LANGUAGE C STRICT; + entries INT +) AS 'pg_lock_hash_info', 'pg_lock_hash_bucket_info' +LANGUAGE C STRICT; diff --git a/pg_lock_hash_info.c b/pg_lock_hash_info.c index c0f8140..bbc490c 100644 --- a/pg_lock_hash_info.c +++ b/pg_lock_hash_info.c @@ -1,3 +1,5 @@ +/* pg_lock_hash_info.c - PostgreSQL extension to expose lock and proclock hash bucket statistics */ + #include "postgres.h" #include "fmgr.h" #include "access/htup_details.h" @@ -5,21 +7,165 @@ #include "storage/lwlock.h" #include "storage/shmem.h" #include "utils/builtins.h" +#include "utils/hsearch.h" +#include "utils/dynahash.h" PG_MODULE_MAGIC; -PG_FUNCTION_INFO_V1(pg_lock_hash_info); +HTAB *lock_hash = NULL; -typedef struct { - int bucket_idx; - char hash_type; - int directory; - int entries; - int free_slots; -} LockHashEntry; +// BEGIN Copied from dynahash.c + +#define DEF_SEGSIZE 256 +#define DEF_SEGSIZE_SHIFT 8 /* must be log2(DEF_SEGSIZE) */ +#define DEF_DIRSIZE 256 +#define NUM_FREELISTS 32 + +typedef HASHELEMENT *HASHBUCKET; +typedef HASHBUCKET *HASHSEGMENT; + +typedef struct +{ + slock_t mutex; /* spinlock for this freelist */ + long nentries; /* number of entries in associated buckets */ + HASHELEMENT *freeList; /* chain of free elements */ +} FreeListData; + +struct HASHHDR +{ + FreeListData freeList[NUM_FREELISTS]; + long dsize; /* directory size */ + long nsegs; /* number of allocated segments (<= dsize) */ + uint32 max_bucket; /* ID of maximum bucket in use */ + uint32 high_mask; /* mask to modulo into entire table */ + uint32 low_mask; /* mask to modulo into lower half of table */ + Size keysize; /* hash key length in bytes */ + Size entrysize; /* total user element size in bytes */ + long num_partitions; /* # partitions (must be power of 2), or 0 */ + long max_dsize; /* 'dsize' limit if directory is fixed size */ + long ssize; /* segment size --- must be power of 2 */ + int sshift; /* segment shift = log2(ssize) */ + int nelem_alloc; /* number of entries to allocate at once */ +#ifdef HASH_STATISTICS + long accesses; + long collisions; +#endif +}; + +struct HTAB +{ + HASHHDR *hctl; /* => shared control information */ + HASHSEGMENT *dir; /* directory of segment starts */ + HashValueFunc hash; /* hash function */ + HashCompareFunc match; /* key comparison function */ + HashCopyFunc keycopy; /* key copying function */ + HashAllocFunc alloc; /* memory allocator */ + MemoryContext hcxt; /* memory context if default allocator used */ + char *tabname; /* table name (for error messages) */ + bool isshared; /* true if table is in shared memory */ + bool isfixed; /* if true, don't enlarge */ + bool frozen; /* true = no more inserts allowed */ + Size keysize; /* hash key length in bytes */ + long ssize; /* segment size --- must be power of 2 */ + int sshift; /* segment shift = log2(ssize) */ +}; + +// END Copied from dynahash.c + +void get_lock_hash(); + +void +get_lock_hash() +{ + HASHCTL info; + lock_hash = ShmemInitHash("LOCK hash", 0, 0, &info, HASH_ATTACH); + if (!lock_hash) + { + elog(ERROR, "Failed to find lock hash"); + } +} + +PG_FUNCTION_INFO_V1(pg_lock_hash_info); Datum pg_lock_hash_info(PG_FUNCTION_ARGS) +{ + TupleDesc tupdesc; + Datum values[10]; + bool nulls[10]; + HeapTuple tuple; + + // Get a pointer to the lock hash + get_lock_hash(); + + if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("function returning record called in context that cannot accept type record"))); + + values[0] = Int32GetDatum(lock_hash->hctl->dsize); + nulls[0] = false; + + values[1] = Int32GetDatum(lock_hash->hctl->nsegs); + nulls[1] = false; + + values[2] = UInt32GetDatum(lock_hash->hctl->max_bucket); + nulls[2] = false; + + values[3] = UInt32GetDatum(lock_hash->hctl->keysize); + nulls[3] = false; + + values[4] = UInt32GetDatum(lock_hash->hctl->entrysize); + nulls[4] = false; + + values[5] = Int32GetDatum(lock_hash->hctl->num_partitions); + nulls[5] = false; + + values[6] = Int32GetDatum(lock_hash->hctl->max_dsize); + nulls[6] = false; + + values[7] = Int32GetDatum(lock_hash->hctl->ssize); + nulls[7] = false; + + values[8] = Int32GetDatum(lock_hash->hctl->sshift); + nulls[8] = false; + + values[9] = Int32GetDatum(lock_hash->hctl->nelem_alloc); + nulls[9] = false; + + tuple = heap_form_tuple(tupdesc, values, nulls); + PG_RETURN_DATUM(HeapTupleGetDatum(tuple)); +} + + + +int count_list(HASHELEMENT *root); + +int +count_list(HASHELEMENT *root) +{ + int count = 0; + HASHELEMENT *elem = root; + + while (elem != NULL) { + count++; + elem = elem->link; + } + + return count; +} + +typedef struct { + int directory; + int bucket; + int bucket_hash; + int entries; +} LockHashEntry; + +PG_FUNCTION_INFO_V1(pg_lock_hash_bucket_info); + +Datum +pg_lock_hash_bucket_info(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; LockHashEntry *entries; @@ -28,39 +174,64 @@ pg_lock_hash_info(PG_FUNCTION_ARGS) { TupleDesc tupdesc; MemoryContext oldcontext; + int i, j; + //HASHSEGMENT segp; + HASHBUCKET bucketp; + + // Get a pointer to the lock hash + get_lock_hash(); funcctx = SRF_FIRSTCALL_INIT(); oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); - tupdesc = CreateTemplateTupleDesc(5); - TupleDescInitEntry(tupdesc, (AttrNumber) 1, "hash_type", TEXTOID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 2, "directory", INT4OID, -1, 0); + tupdesc = CreateTemplateTupleDesc(4); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "directory", INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "bucket", INT4OID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 3, "bucket_hash", INT4OID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber) 4, "entries", INT4OID, -1, 0); - TupleDescInitEntry(tupdesc, (AttrNumber) 5, "free_slots", INT4OID, -1, 0); funcctx->tuple_desc = BlessTupleDesc(tupdesc); - /* Allocate space for results (this should fetch real data) */ - entries = palloc(sizeof(LockHashEntry) * 10); /* Placeholder for real count */ + /* Allocate and initialize the entries */ + entries = (LockHashEntry *) palloc(sizeof(LockHashEntry) * lock_hash->hctl->nsegs * lock_hash->hctl->max_bucket); + for (i = 0; i < lock_hash->hctl->nsegs; i++) + { + for (j = 0; j < lock_hash->hctl->max_bucket; j++) + { + bucketp = lock_hash->dir[i][j]; + entries[i].directory = i; + entries[i].bucket = j; + entries[i].bucket_hash = bucketp ? bucketp->hashvalue : 'na'; + entries[i].entries = count_list(bucketp); + } + } + funcctx->user_fctx = entries; + funcctx->max_calls = lock_hash->hctl->nsegs * 10; // * lock_hash->hctl->max_bucket; + MemoryContextSwitchTo(oldcontext); } funcctx = SRF_PERCALL_SETUP(); entries = (LockHashEntry *) funcctx->user_fctx; - /* Placeholder loop to return mock data */ - if (funcctx->call_cntr < 10) + if (funcctx->call_cntr < funcctx->max_calls) { - Datum values[5]; - bool nulls[5] = {false, false, false, false, false}; + Datum values[4]; + bool nulls[4]; HeapTuple tuple; + LockHashEntry *entry = &entries[funcctx->call_cntr]; - values[0] = CharGetDatum(funcctx->call_cntr % 2 == 0 ? 'l' : 'p'); - values[1] = Int32GetDatum(funcctx->call_cntr / 5); - values[2] = Int32GetDatum(funcctx->call_cntr); - values[3] = Int32GetDatum(5); /* Fake number of entries */ - values[4] = Int32GetDatum(10); /* Fake number of free slots */ + values[0] = Int32GetDatum(entry->directory); + nulls[0] = false; + + values[1] = Int32GetDatum(entry->bucket); + nulls[1] = false; + + values[2] = Int32GetDatum(entry->bucket_hash); + nulls[2] = false; + + values[3] = Int32GetDatum(entry->entries); + nulls[3] = false; tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));