/* 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" #include "funcapi.h" #include "storage/lwlock.h" #include "storage/shmem.h" #include "utils/builtins.h" #include "utils/hsearch.h" #include "utils/dynahash.h" PG_MODULE_MAGIC; HTAB *lock_hash = NULL; // 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; if (SRF_IS_FIRSTCALL()) { 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(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); funcctx->tuple_desc = BlessTupleDesc(tupdesc); /* 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; if (funcctx->call_cntr < funcctx->max_calls) { Datum values[4]; bool nulls[4]; HeapTuple tuple; LockHashEntry *entry = &entries[funcctx->call_cntr]; 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)); } else { SRF_RETURN_DONE(funcctx); } }