diff --git a/pg_lock_hash_info--1.0.sql b/pg_lock_hash_info--1.0.sql index 734eaaf..23a7921 100644 --- a/pg_lock_hash_info--1.0.sql +++ b/pg_lock_hash_info--1.0.sql @@ -9,15 +9,57 @@ RETURNS TABLE ( max_dsize INT, ssize INT, sshift INT, - nelem_alloc INT + nelem_alloc INT, + nfree INT, + nlockents INT, + low_mask INT, + high_mask INT ) AS 'pg_lock_hash_info', 'pg_lock_hash_info' LANGUAGE C STRICT; +CREATE FUNCTION pg_lock_hash_freelist_info() +RETURNS TABLE ( + freelist INT, + nentries INT, + count INT +) AS 'pg_lock_hash_info', 'pg_lock_hash_freelist_info' +LANGUAGE C STRICT; + CREATE FUNCTION pg_lock_hash_bucket_info() RETURNS TABLE ( - directory INT, + segment INT, bucket INT, bucket_hash INT, + bucket_hash_low INT, + bucket_hash_high INT, entries INT ) AS 'pg_lock_hash_info', 'pg_lock_hash_bucket_info' LANGUAGE C STRICT; + + +CREATE VIEW pg_lock_hash_info AS +SELECT * from pg_lock_hash_info(); + +CREATE VIEW pg_lock_hash_freelist_info AS +SELECT * FROM pg_lock_hash_freelist_info(); + +CREATE VIEW pg_lock_hash_bucket_info AS +SELECT * from pg_lock_hash_bucket_info(); + +CREATE VIEW pg_lock_hash_freelist_summary AS +SELECT count(*) AS freelist_total, + sum(nentries) AS nentries_total, + avg(nentries) AS nentries_avg, + stddev_pop(nentries) nentries_stddev, + sum(count) AS count_total, + avg(count) AS count_avg, + stddev_pop(count) AS count_stddev, + sum(nentries+count) AS total +FROM pg_lock_hash_freelist_info(); + +CREATE VIEW pg_lock_hash_bucket_summary AS +SELECT count(*) AS buckets_total, + sum(entries) AS total_active_locks, + avg(entries) AS avg_bucket_size, + stddev_pop(entries) AS stddev_bucket_size +FROM pg_lock_hash_bucket_info(); diff --git a/pg_lock_hash_info.c b/pg_lock_hash_info.c index bbc490c..41b471a 100644 --- a/pg_lock_hash_info.c +++ b/pg_lock_hash_info.c @@ -3,9 +3,13 @@ #include "postgres.h" #include "fmgr.h" #include "access/htup_details.h" +#include "miscadmin.h" // Access to MaxBackends and MaxConnections +#include "access/twophase.h" // Access to max_prepared_xacts #include "funcapi.h" +#include "storage/lock.h" #include "storage/lwlock.h" #include "storage/shmem.h" +#include "storage/spin.h" #include "utils/builtins.h" #include "utils/hsearch.h" #include "utils/dynahash.h" @@ -14,8 +18,13 @@ PG_MODULE_MAGIC; HTAB *lock_hash = NULL; +void get_lock_hash(); + // BEGIN Copied from dynahash.c +#define NLOCKENTS() \ + mul_size(max_locks_per_xact, add_size(MaxBackends, max_prepared_xacts)) + #define DEF_SEGSIZE 256 #define DEF_SEGSIZE_SHIFT 8 /* must be log2(DEF_SEGSIZE) */ #define DEF_DIRSIZE 256 @@ -72,27 +81,69 @@ struct HTAB // 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); + long init_table_size, max_table_size; + bool found; + + max_table_size = NLOCKENTS(); + init_table_size = max_table_size / 2; + + info.keysize = sizeof(LOCKTAG); + info.entrysize = sizeof(LOCK); + info.num_partitions = NUM_LOCK_PARTITIONS; + + lock_hash = ShmemInitHash("LOCK hash", init_table_size, max_table_size, &info, HASH_ELEM | HASH_BLOBS | HASH_PARTITION); + if (!lock_hash) { elog(ERROR, "Failed to find lock hash"); } } +int +get_free_count(FreeListData *lst) +{ + HASHELEMENT *elem; + int count = 0; + + SpinLockAcquire(&(lst->mutex)); + elem = lst->freeList; + while (elem != NULL) + { + count++; + elem = elem->link; + } + SpinLockRelease(&(lst->mutex)); + + return count; +} + +int +get_total_free_count(HASHHDR *hctl) +{ + int i; + int count = 0; + + for (i=0; ifreeList[i])); + } + + return count; +} + + PG_FUNCTION_INFO_V1(pg_lock_hash_info); Datum pg_lock_hash_info(PG_FUNCTION_ARGS) { TupleDesc tupdesc; - Datum values[10]; - bool nulls[10]; + Datum values[14]; + bool nulls[14]; HeapTuple tuple; // Get a pointer to the lock hash @@ -104,9 +155,11 @@ pg_lock_hash_info(PG_FUNCTION_ARGS) errmsg("function returning record called in context that cannot accept type record"))); values[0] = Int32GetDatum(lock_hash->hctl->dsize); + //values[0] = Int32GetDatum(MaxBackends); nulls[0] = false; values[1] = Int32GetDatum(lock_hash->hctl->nsegs); + //values[1] = Int32GetDatum(MaxConnections); nulls[1] = false; values[2] = UInt32GetDatum(lock_hash->hctl->max_bucket); @@ -133,19 +186,102 @@ pg_lock_hash_info(PG_FUNCTION_ARGS) values[9] = Int32GetDatum(lock_hash->hctl->nelem_alloc); nulls[9] = false; + values[10] = Int32GetDatum(get_total_free_count(lock_hash->hctl)); + nulls[10] = false; + + values[11] = Int32GetDatum(NLOCKENTS()); + nulls[11] = false; + + values[12] = Int32GetDatum(lock_hash->hctl->low_mask); + nulls[12] = false; + + values[13] = Int32GetDatum(lock_hash->hctl->high_mask); + nulls[13] = false; + tuple = heap_form_tuple(tupdesc, values, nulls); PG_RETURN_DATUM(HeapTupleGetDatum(tuple)); } -int count_list(HASHELEMENT *root); +PG_FUNCTION_INFO_V1(pg_lock_hash_freelist_info); + +Datum +pg_lock_hash_freelist_info(PG_FUNCTION_ARGS) +{ + FuncCallContext *funcctx; + int *list_id; + + if (SRF_IS_FIRSTCALL()) + { + TupleDesc tupdesc; + MemoryContext oldcontext; + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + // Get a pointer to the lock hash + get_lock_hash(); + + tupdesc = CreateTemplateTupleDesc(3); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "freelist", INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "nentries", INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "count", INT4OID, -1, 0); + funcctx->tuple_desc = BlessTupleDesc(tupdesc); + + /* Allocate and initialize the position */ + funcctx->user_fctx = palloc(sizeof(int)); + list_id = (int *) funcctx->user_fctx; + *list_id = 0; + + MemoryContextSwitchTo(oldcontext); + } + + funcctx = SRF_PERCALL_SETUP(); + list_id = (int *) funcctx->user_fctx; + + if (*list_id >= NUM_FREELISTS) + { + SRF_RETURN_DONE(funcctx); + } + else + { + Datum values[4]; + bool nulls[4]; + HeapTuple tuple; + FreeListData *freelist = &(lock_hash->hctl->freeList[*list_id]); + + values[0] = Int32GetDatum(*list_id); + nulls[0] = false; + + values[1] = Int32GetDatum(freelist->nentries); + nulls[1] = false; + + values[2] = Int32GetDatum(get_free_count(freelist)); + nulls[2] = false; + + tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); + + // Increment the position, handling segment wraparound + (*list_id)++; + + SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); + } +} + int -count_list(HASHELEMENT *root) +count_list(HASHBUCKET root) { int count = 0; - HASHELEMENT *elem = root; + HASHELEMENT *elem; + + if (!root) + { + return 0; + } + + elem = root->link; while (elem != NULL) { count++; @@ -155,90 +291,113 @@ count_list(HASHELEMENT *root) return count; } -typedef struct { - int directory; - int bucket; - int bucket_hash; - int entries; -} LockHashEntry; PG_FUNCTION_INFO_V1(pg_lock_hash_bucket_info); +typedef struct { + int segment_id; + int bucket_id; +} LockHashBucketInfoPosition; + Datum pg_lock_hash_bucket_info(PG_FUNCTION_ARGS) { FuncCallContext *funcctx; - LockHashEntry *entries; + LockHashBucketInfoPosition *pos; + LWLock *part_lock; 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); + // Get a pointer to the lock hash + get_lock_hash(); + + tupdesc = CreateTemplateTupleDesc(6); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "segment", 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) 4, "bucket_hash_low", INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 5, "bucket_hash_high", INT4OID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 6, "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; + /* Allocate and initialize the position */ + funcctx->user_fctx = palloc(sizeof(LockHashBucketInfoPosition)); + pos = (LockHashBucketInfoPosition *) funcctx->user_fctx; + pos->segment_id = 0; + pos->bucket_id = 0; MemoryContextSwitchTo(oldcontext); } funcctx = SRF_PERCALL_SETUP(); - entries = (LockHashEntry *) funcctx->user_fctx; + pos = (LockHashBucketInfoPosition *) 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 + if (pos->segment_id >= lock_hash->hctl->nsegs) { SRF_RETURN_DONE(funcctx); } + else + { + Datum values[6]; + bool nulls[6]; + HeapTuple tuple; + HASHBUCKET bucketp = lock_hash->dir[pos->segment_id][pos->bucket_id]; + + values[0] = Int32GetDatum(pos->segment_id); + nulls[0] = false; + + values[1] = Int32GetDatum(pos->bucket_id); + nulls[1] = false; + + if (bucketp) + { + values[2] = Int32GetDatum(bucketp->hashvalue); + nulls[2] = false; + + values[3] = Int32GetDatum(bucketp->hashvalue & lock_hash->hctl->low_mask); + nulls[3] = false; + + values[4] = Int32GetDatum(bucketp->hashvalue & lock_hash->hctl->high_mask); + nulls[4] = false; + + part_lock = LockHashPartitionLock(bucketp->hashvalue); + LWLockAcquire(part_lock, LW_SHARED); + values[5] = UInt32GetDatum(count_list(bucketp)); + nulls[5] = false; + LWLockRelease(part_lock); + } + else + { + values[2] = Int32GetDatum(0); + nulls[2] = true; + + values[3] = Int32GetDatum(0); + nulls[3] = true; + + values[4] = Int32GetDatum(0); + nulls[4] = true; + + values[5] = Int32GetDatum(0); + nulls[5] = false; + } + + tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); + + // Increment the position, handling segment wraparound + pos->bucket_id++; + while (pos->bucket_id >= lock_hash->hctl->ssize) + { + pos->bucket_id -= lock_hash->hctl->ssize; + pos->segment_id++; + } + + SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); + } }