I've been working on the TODO list item "Add SHOW command to display locks". The code is basically finished, but I'd like to make sure the user interface is okay with everyone before I send it in to -patches (if you're interested, the patch is attached).
Rather than adding another SHOW command, I think using a table function is a better idea. That's because the information returned by the lock listing code will often need to be correlated with other information in the system catalogs, or sorted/aggregated in various ways (e.g. "show me the names of all locked relations", or "show me the relation with the most AccessShareLocks'"). Written as a table function, the lock listing code itself can be fairly simple, and the DBA can write the necessary SQL queries to produce the information he needs. It also makes it easier to parse the lock status information, if you're writing (for example) a GUI admin tool. Usage examples: Basic information returned from function: nconway=# select * from show_locks(); relation | database | backendpid | mode | isgranted ----------+----------+------------+-----------------+----------- 16575 | 16689 | 13091 | AccessShareLock | t 376 | 0 | 13091 | ExclusiveLock | t After creating a simple relation and starting 2 transactions, one of which has acquired the lock and one which is waiting on it: nconway=# select l.backendpid, l.mode, l.isgranted from show_locks() l, pg_class c where l.relation = c.oid and c.relname = 'a'; backendpid | mode | isgranted ------------+-----------------------+----------- 13098 | RowExclusiveLock | t 13108 | ShareRowExclusiveLock | f During a 128 client pgbench run: pgbench1=# select c.relname, count(l.isgranted) from show_locks() l, pg_class c where c.oid = l.relation group by c.relname order by count desc; relname | count ---------------------+------- accounts | 1081 tellers | 718 pg_xactlock | 337 branches | 208 history | 4 pg_class | 3 __show_locks_result | 1 And so on -- I think you get the idea. Regarding performance, the only performance-critical aspect of the patch is the place where we need to acquire the LockMgrLock, to ensure that we get a consistent view of data from the lock manager's hash tables. The patch is designed so that this lock is held for as short a period as possible: the lock is acquired, the data is copied from shared memory to local memory, the lock is released, and then the data is processed. Any suggestions on how to optimize performance any further would be welcome. Let me know if there are any objections or suggestions for improvement. In particular, should we provide some pre-defined views that correlate the show_locks() data with data from the system catalogs? And if so, which views should be pre-defined? Also, should locks on special relations (e.g. pg_xactlock) or on system catalogs be shown? Cheers, Neil -- Neil Conway <[EMAIL PROTECTED]> PGP Key ID: DB3C29FC
Index: src/backend/storage/lmgr/lmgr.c =================================================================== RCS file: /var/lib/cvs/pgsql/src/backend/storage/lmgr/lmgr.c,v retrieving revision 1.53 diff -c -r1.53 lmgr.c *** src/backend/storage/lmgr/lmgr.c 20 Jun 2002 20:29:35 -0000 1.53 --- src/backend/storage/lmgr/lmgr.c 18 Jul 2002 17:38:13 -0000 *************** *** 104,118 **** if (!(LockTableId)) elog(ERROR, "InitLockTable: couldn't initialize lock table"); - #ifdef USER_LOCKS - /* * Allocate another tableId for long-term locks */ LongTermTableId = LockMethodTableRename(LockTableId); if (!(LongTermTableId)) elog(ERROR, "InitLockTable: couldn't rename long-term lock table"); - #endif return LockTableId; } --- 104,115 ---- Index: src/backend/storage/lmgr/lock.c =================================================================== RCS file: /var/lib/cvs/pgsql/src/backend/storage/lmgr/lock.c,v retrieving revision 1.108 diff -c -r1.108 lock.c *** src/backend/storage/lmgr/lock.c 20 Jun 2002 20:29:35 -0000 1.108 --- src/backend/storage/lmgr/lock.c 18 Jul 2002 17:38:13 -0000 *************** *** 1053,1063 **** if (!holder) { LWLockRelease(masterLock); - #ifdef USER_LOCKS if (lockmethod == USER_LOCKMETHOD) elog(WARNING, "LockRelease: no lock with this tag"); else - #endif elog(WARNING, "LockRelease: holder table corrupted"); return FALSE; } --- 1053,1061 ---- *************** *** 1373,1378 **** --- 1371,1442 ---- return size; } + /* + * GetLockStatusData - Return a summary of the lock manager's internal + * status, for use in a user-level statistical reporting function. + * + * This function should be passed a pointer to a LockData struct. It fills + * the structure with the appropriate information and returns. The goal + * is to hold the LockMgrLock for as short a time as possible; thus, the + * function simply makes a copy of the necessary data and releases the + * lock, allowing the caller to contemplate and format the data for + * as long as it pleases. + */ + void + GetLockStatusData(LockData *data) + { + HTAB *holderTable; + HOLDER *holder; + int i = 0; + HASH_SEQ_STATUS seqstat; + + data->currIdx = 0; + + LWLockAcquire(LockMgrLock, LW_EXCLUSIVE); + + holderTable = LockMethodTable[DEFAULT_LOCKMETHOD]->holderHash; + + data->nelements = holderTable->hctl->nentries; + + data->holders = (HOLDER **) palloc(sizeof(HOLDER *) * data->nelements); + data->procs = (PGPROC **) palloc(sizeof(PGPROC *) * data->nelements); + data->locks = (LOCK **) palloc(sizeof(LOCK *) * data->nelements); + + hash_seq_init(&seqstat, holderTable); + + while ( (holder = hash_seq_search(&seqstat)) ) + { + PGPROC *proc; + LOCK *lock; + + data->holders[i] = (HOLDER *) palloc(sizeof(HOLDER)); + data->procs[i] = (PGPROC *) palloc(sizeof(PGPROC)); + data->locks[i] = (LOCK *) palloc(sizeof(LOCK)); + + /* Only do a shallow copy */ + memcpy(data->holders[i], holder, sizeof(HOLDER)); + + proc = (PGPROC *) MAKE_PTR(holder->tag.proc); + + memcpy(data->procs[i], proc, sizeof(PGPROC)); + + lock = (LOCK *) MAKE_PTR(holder->tag.lock); + + memcpy(data->locks[i], lock, sizeof(LOCK)); + + i++; + } + + Assert(i == data->nelements); + + LWLockRelease(LockMgrLock); + } + + char * + GetLockmodeName(LOCKMODE mode) + { + return lock_mode_names[mode]; + } #ifdef LOCK_DEBUG /* Index: src/backend/storage/lmgr/proc.c =================================================================== RCS file: /var/lib/cvs/pgsql/src/backend/storage/lmgr/proc.c,v retrieving revision 1.122 diff -c -r1.122 proc.c *** src/backend/storage/lmgr/proc.c 13 Jul 2002 01:02:14 -0000 1.122 --- src/backend/storage/lmgr/proc.c 18 Jul 2002 17:38:13 -0000 *************** *** 397,406 **** /* Remove from the standard lock table */ LockReleaseAll(DEFAULT_LOCKMETHOD, MyProc, true, InvalidTransactionId); - #ifdef USER_LOCKS /* Remove from the user lock table */ LockReleaseAll(USER_LOCKMETHOD, MyProc, true, InvalidTransactionId); - #endif SpinLockAcquire(ProcStructLock); --- 397,404 ---- Index: src/backend/tcop/utility.c =================================================================== RCS file: /var/lib/cvs/pgsql/src/backend/tcop/utility.c,v retrieving revision 1.163 diff -c -r1.163 utility.c *** src/backend/tcop/utility.c 18 Jul 2002 16:47:25 -0000 1.163 --- src/backend/tcop/utility.c 18 Jul 2002 17:38:13 -0000 *************** *** 217,224 **** break; /* ! * ******************************** portal manipulation ******************************** ! * */ case T_ClosePortalStmt: { --- 217,223 ---- break; /* ! * ************************* portal manipulation *************************** */ case T_ClosePortalStmt: { Index: src/backend/utils/adt/Makefile =================================================================== RCS file: /var/lib/cvs/pgsql/src/backend/utils/adt/Makefile,v retrieving revision 1.51 diff -c -r1.51 Makefile *** src/backend/utils/adt/Makefile 4 Oct 2001 04:13:40 -0000 1.51 --- src/backend/utils/adt/Makefile 18 Jul 2002 17:38:13 -0000 *************** *** 17,23 **** OBJS = acl.o arrayfuncs.o arrayutils.o bool.o cash.o char.o \ date.o datetime.o datum.o float.o format_type.o \ ! geo_ops.o geo_selfuncs.o int.o int8.o like.o \ misc.o nabstime.o name.o not_in.o numeric.o numutils.o \ oid.o oracle_compat.o \ regexp.o regproc.o ruleutils.o selfuncs.o sets.o \ --- 17,23 ---- OBJS = acl.o arrayfuncs.o arrayutils.o bool.o cash.o char.o \ date.o datetime.o datum.o float.o format_type.o \ ! geo_ops.o geo_selfuncs.o int.o int8.o like.o lockfuncs.o \ misc.o nabstime.o name.o not_in.o numeric.o numutils.o \ oid.o oracle_compat.o \ regexp.o regproc.o ruleutils.o selfuncs.o sets.o \ Index: src/bin/initdb/initdb.sh =================================================================== RCS file: /var/lib/cvs/pgsql/src/bin/initdb/initdb.sh,v retrieving revision 1.160 diff -c -r1.160 initdb.sh *** src/bin/initdb/initdb.sh 18 Jul 2002 16:47:25 -0000 1.160 --- src/bin/initdb/initdb.sh 18 Jul 2002 17:38:13 -0000 *************** *** 763,770 **** FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) \ WHERE C.relkind = 'v'; - -- XXX why does pg_tables include sequences? - CREATE VIEW pg_tables AS \ SELECT \ C.relname AS tablename, \ --- 763,768 ---- *************** *** 969,974 **** --- 967,985 ---- pg_stat_get_db_blocks_hit(D.oid) AS blks_read, \ pg_stat_get_db_blocks_hit(D.oid) AS blks_hit \ FROM pg_database D; + + CREATE VIEW __show_locks_result AS \ + SELECT \ + ''::oid AS relation, \ + ''::oid AS database, \ + ''::int4 AS backendpid, \ + ''::text AS mode, \ + NULL::bool AS isgranted; + + UPDATE pg_proc SET \ + prorettype = (SELECT oid FROM pg_type \ + WHERE typname = '__show_locks_result') \ + WHERE proname = 'show_locks'; EOF if [ "$?" -ne 0 ]; then Index: src/include/pg_config.h.in =================================================================== RCS file: /var/lib/cvs/pgsql/src/include/pg_config.h.in,v retrieving revision 1.24 diff -c -r1.24 pg_config.h.in *** src/include/pg_config.h.in 5 May 2002 00:03:29 -0000 1.24 --- src/include/pg_config.h.in 18 Jul 2002 17:38:13 -0000 *************** *** 180,194 **** /* #define TCL_ARRAYS */ /* - * User locks are handled totally on the application side as long term - * cooperative locks which extend beyond the normal transaction boundaries. - * Their purpose is to indicate to an application that someone is `working' - * on an item. Define this flag to enable user locks. You will need the - * loadable module user-locks.c to use this feature. - */ - #define USER_LOCKS - - /* * Define this if you want psql to _always_ ask for a username and a password * for password authentication. */ --- 180,185 ---- Index: src/include/catalog/catversion.h =================================================================== RCS file: /var/lib/cvs/pgsql/src/include/catalog/catversion.h,v retrieving revision 1.140 diff -c -r1.140 catversion.h *** src/include/catalog/catversion.h 15 Jul 2002 16:33:31 -0000 1.140 --- src/include/catalog/catversion.h 18 Jul 2002 17:38:13 -0000 *************** *** 53,58 **** */ /* yyyymmddN */ ! #define CATALOG_VERSION_NO 200207141 #endif --- 53,58 ---- */ /* yyyymmddN */ ! #define CATALOG_VERSION_NO 200207181 #endif Index: src/include/catalog/pg_proc.h =================================================================== RCS file: /var/lib/cvs/pgsql/src/include/catalog/pg_proc.h,v retrieving revision 1.243 diff -c -r1.243 pg_proc.h *** src/include/catalog/pg_proc.h 20 Jun 2002 20:29:44 -0000 1.243 --- src/include/catalog/pg_proc.h 18 Jul 2002 17:38:13 -0000 *************** *** 2681,2686 **** --- 2681,2689 ---- DATA(insert OID = 1915 ( numeric_uplus PGNSP PGUID 12 f f f t f i 1 1700 "1700" 100 0 0 100 numeric_uplus - _null_ )); DESCR("unary plus"); + DATA(insert OID = 1920 ( show_locks PGNSP PGUID 12 f f f t t v 0 0 "0" 100 0 0 +100 show_locks_srf - _null_ )); + DESCR("view system lock information"); + DATA(insert OID = 1922 ( has_table_privilege PGNSP PGUID 12 f f f t f s 3 16 "19 25 25" 100 0 0 100 has_table_privilege_name_name - _null_ )); DESCR("user privilege on relation by username, relname"); DATA(insert OID = 1923 ( has_table_privilege PGNSP PGUID 12 f f f t f s 3 16 "19 26 25" 100 0 0 100 has_table_privilege_name_id - _null_ )); Index: src/include/storage/lock.h =================================================================== RCS file: /var/lib/cvs/pgsql/src/include/storage/lock.h,v retrieving revision 1.61 diff -c -r1.61 lock.h *** src/include/storage/lock.h 20 Jun 2002 20:29:52 -0000 1.61 --- src/include/storage/lock.h 18 Jul 2002 17:38:13 -0000 *************** *** 59,66 **** #define USER_LOCKMETHOD 2 /* ! * There is normally only one lock method, the default one. ! * If user locks are enabled, an additional lock method is present. * * LOCKMETHODCTL and LOCKMETHODTABLE are split because the first lives * in shared memory. (There isn't any really good reason for the split.) --- 59,66 ---- #define USER_LOCKMETHOD 2 /* ! * There are currently two lock methods: the default method, and the method ! * used for user locks. * * LOCKMETHODCTL and LOCKMETHODTABLE are split because the first lives * in shared memory. (There isn't any really good reason for the split.) *************** *** 222,227 **** --- 222,242 ---- #define HOLDER_LOCKMETHOD(holder) \ (((LOCK *) MAKE_PTR((holder).tag.lock))->tag.lockmethod) + /* + * This struct is used to encapsulate information passed from lmgr + * internals to the lock listing statistical functions (lockfuncs.c). + * It's just a convenient bundle of other lock.h structures. All + * the information at a given index (holders[i], procs[i], locks[i]) + * is related. + */ + typedef struct + { + int nelements; /* The length of holders, procs, & locks */ + int currIdx; /* Current element being examined */ + HOLDER **holders; + PGPROC **procs; + LOCK **locks; + } LockData; /* * function prototypes *************** *** 246,251 **** --- 261,268 ---- extern int LockShmemSize(int maxBackends); extern bool DeadLockCheck(PGPROC *proc); extern void InitDeadLockChecking(void); + extern void GetLockStatusData(LockData *data); + extern char *GetLockmodeName(LOCKMODE mode); #ifdef LOCK_DEBUG extern void DumpLocks(void); Index: src/include/storage/shmem.h =================================================================== RCS file: /var/lib/cvs/pgsql/src/include/storage/shmem.h,v retrieving revision 1.37 diff -c -r1.37 shmem.h *** src/include/storage/shmem.h 20 Jun 2002 20:29:52 -0000 1.37 --- src/include/storage/shmem.h 18 Jul 2002 17:38:13 -0000 *************** *** 53,60 **** #define SHM_OFFSET_VALID(xx_offs)\ (((xx_offs) != 0) && ((xx_offs) != INVALID_OFFSET)) ! ! /* shmemqueue.c */ typedef struct SHM_QUEUE { SHMEM_OFFSET prev; --- 53,59 ---- #define SHM_OFFSET_VALID(xx_offs)\ (((xx_offs) != 0) && ((xx_offs) != INVALID_OFFSET)) ! /* shmqueue.c */ typedef struct SHM_QUEUE { SHMEM_OFFSET prev; Index: src/test/regress/expected/rules.out =================================================================== RCS file: /var/lib/cvs/pgsql/src/test/regress/expected/rules.out,v retrieving revision 1.53 diff -c -r1.53 rules.out *** src/test/regress/expected/rules.out 3 May 2002 00:32:19 -0000 1.53 --- src/test/regress/expected/rules.out 18 Jul 2002 17:38:13 -0000 *************** *** 1266,1271 **** --- 1266,1272 ---- SELECT viewname, definition FROM pg_views ORDER BY viewname; viewname | definition --------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + __show_locks_result | SELECT 0::oid AS relation, 0::oid AS "database", 0 AS +backendpid, ''::text AS "mode", NULL::boolean AS isgranted; iexit | SELECT ih.name, ih.thepath, interpt_pp(ih.thepath, r.thepath) AS exit FROM ihighway ih, ramp r WHERE (ih.thepath ## r.thepath); pg_indexes | SELECT c.relname AS tablename, i.relname AS indexname, pg_get_indexdef(x.indexrelid) AS indexdef FROM pg_index x, pg_class c, pg_class i WHERE ((((c.relkind = 'r'::"char") AND (i.relkind = 'i'::"char")) AND (c.oid = x.indrelid)) AND (i.oid = x.indexrelid)); pg_rules | SELECT n.nspname AS schemaname, c.relname AS tablename, r.rulename, pg_get_ruledef(r.oid) AS definition FROM ((pg_rewrite r JOIN pg_class c ON ((c.oid = r.ev_class))) LEFT JOIN pg_namespace n ON ((n.oid = c.relnamespace))) WHERE (r.rulename <> '_RETURN'::name); *************** *** 1304,1310 **** shoelace_obsolete | SELECT shoelace.sl_name, shoelace.sl_avail, shoelace.sl_color, shoelace.sl_len, shoelace.sl_unit, shoelace.sl_len_cm FROM shoelace WHERE (NOT (EXISTS (SELECT shoe.shoename FROM shoe WHERE (shoe.slcolor = shoelace.sl_color)))); street | SELECT r.name, r.thepath, c.cname FROM ONLY road r, real_city c WHERE (c.outline ## r.thepath); toyemp | SELECT emp.name, emp.age, emp."location", (12 * emp.salary) AS annualsal FROM emp; ! (38 rows) SELECT tablename, rulename, definition FROM pg_rules ORDER BY tablename, rulename; --- 1305,1311 ---- shoelace_obsolete | SELECT shoelace.sl_name, shoelace.sl_avail, shoelace.sl_color, shoelace.sl_len, shoelace.sl_unit, shoelace.sl_len_cm FROM shoelace WHERE (NOT (EXISTS (SELECT shoe.shoename FROM shoe WHERE (shoe.slcolor = shoelace.sl_color)))); street | SELECT r.name, r.thepath, c.cname FROM ONLY road r, real_city c WHERE (c.outline ## r.thepath); toyemp | SELECT emp.name, emp.age, emp."location", (12 * emp.salary) AS annualsal FROM emp; ! (39 rows) SELECT tablename, rulename, definition FROM pg_rules ORDER BY tablename, rulename;
---------------------------(end of broadcast)--------------------------- TIP 5: Have you checked our extensive FAQ? http://www.postgresql.org/users-lounge/docs/faq.html