You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudberry.apache.org by ch...@apache.org on 2026/03/13 03:14:30 UTC
(cloudberry) 01/03: feat: add pg_stat_resqueues view for resource queue statistics
This is an automated email from the ASF dual-hosted git repository.
chenjinbao1989 pushed a commit to branch cbdb-postgres-merge
in repository https://gitbox.apache.org/repos/asf/cloudberry.git
commit 32c515ed94aded92566ee727fad4ef860ea635d0
Author: liushengsong <ls...@gmail.com>
AuthorDate: Fri Mar 6 09:51:20 2026 +0800
feat: add pg_stat_resqueues view for resource queue statistics
Adds a new PGSTAT_KIND_RESQUEUE statistics kind that tracks per-queue
throughput and latency counters, exposed through the pg_stat_resqueues
view and pg_stat_get_resqueue_stats() SQL function.
New statistics tracked per resource queue:
- queries_submitted / admitted / rejected / completed
- elapsed_wait_secs / max_wait_secs
- elapsed_exec_secs / max_exec_secs
- total_cost / total_memory_kb
- stat_reset_timestamp
Implementation notes:
- All stat updates use pgstat_get_entry_ref_locked() to write directly
to shared memory, bypassing the pending/flush mechanism. This makes
stats immediately visible without explicit flushes, and is safe when
called from PG_CATCH blocks (pgstat_resqueue_rejected is invoked
inside PG_CATCH in ResLockPortal/ResLockUtilityPortal).
- Per-portal timing is tracked in a backend-local hash table keyed by
portalid; entries are removed when the portal is admitted, rejected,
or completed.
Also fixes a pre-existing bug in resqueue.c: dclist_delete_from()
already decrements waitProcs.count internally, but three call sites
additionally did a manual count-- causing count to underflow to
UINT32_MAX. The next dclist_push_tail() would then overflow count
back to 0 and trip the overflow assertion in ResProcSleep, crashing
the backend.
New files:
- src/backend/utils/activity/pgstat_resqueue.c
- src/test/isolation2/sql/resqueue_stats.sql
- src/test/isolation2/expected/resqueue_stats.out
Co-Authored-By: Claude Sonnet 4.6 <no...@anthropic.com>
---
src/backend/catalog/system_views.sql | 19 +
src/backend/postmaster/autovacuum.c | 7 +-
src/backend/utils/activity/Makefile | 1 +
src/backend/utils/activity/pgstat.c | 16 +
src/backend/utils/activity/pgstat_resqueue.c | 388 +++++++++++++++++++++
src/backend/utils/adt/pgstatfuncs.c | 78 +++++
src/backend/utils/resscheduler/resqueue.c | 9 +-
src/backend/utils/resscheduler/resscheduler.c | 64 +++-
src/include/catalog/pg_proc.dat | 8 +
src/include/pgstat.h | 74 +++-
src/include/utils/pgstat_internal.h | 31 ++
.../expected/resource_queue_deadlock.out | 8 +-
.../expected/resource_queue_multi_portal.out | 6 +-
src/test/isolation2/expected/resqueue_stats.out | 92 +++++
src/test/isolation2/init_file_isolation2 | 4 +
.../isolation2/sql/resource_queue_deadlock.sql | 2 +
src/test/isolation2/sql/resqueue_stats.sql | 72 ++++
17 files changed, 850 insertions(+), 29 deletions(-)
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 505c817bc07..d4f0474cdc0 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -1224,6 +1224,25 @@ CREATE VIEW pg_resqueue_status AS
queueholders int4)
ON (s.queueid = q.oid);
+-- Resource queue cumulative statistics view
+CREATE VIEW pg_stat_resqueues AS
+ SELECT
+ q.oid AS queueid,
+ q.rsqname AS queuename,
+ s.queries_submitted,
+ s.queries_admitted,
+ s.queries_rejected,
+ s.queries_completed,
+ s.elapsed_wait_secs AS total_wait_time_secs,
+ s.max_wait_secs,
+ s.elapsed_exec_secs AS total_exec_time_secs,
+ s.max_exec_secs,
+ s.total_cost,
+ s.total_memory_kb,
+ s.stat_reset_timestamp
+ FROM pg_resqueue AS q,
+ pg_stat_get_resqueue_stats(q.oid) AS s;
+
-- External table views
CREATE VIEW pg_max_external_files AS
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 7372a51a373..f517cb4d006 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -676,16 +676,17 @@ AutoVacLauncherMain(int argc, char *argv[])
* the database chosen is connectable, the launcher will never select it and the
* worker will continue to signal for a new launcher.
*/
-#if 0
/*
* Even when system is configured to use a different fetch consistency,
* for autovac we always want fresh stats.
*/
SetConfigOption("stats_fetch_consistency", "none", PGC_SUSET, PGC_S_OVERRIDE);
+#if 0
/*
- * In emergency mode, just start a worker (unless shutdown was requested)
- * and go away.
+ * In GPDB, we only want an autovacuum worker to start once we know
+ * there is a database to vacuum. Therefore, we never want emergency mode
+ * to start a worker immediately.
*/
if (!AutoVacuumingActive())
{
diff --git a/src/backend/utils/activity/Makefile b/src/backend/utils/activity/Makefile
index 7d7482dde02..d7d7d6c6b0d 100644
--- a/src/backend/utils/activity/Makefile
+++ b/src/backend/utils/activity/Makefile
@@ -25,6 +25,7 @@ OBJS = \
pgstat_io.o \
pgstat_relation.o \
pgstat_replslot.o \
+ pgstat_resqueue.o \
pgstat_shmem.o \
pgstat_slru.o \
pgstat_subscription.o \
diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c
index 20c3f3bbb40..ab7768154c2 100644
--- a/src/backend/utils/activity/pgstat.c
+++ b/src/backend/utils/activity/pgstat.c
@@ -337,6 +337,22 @@ static const PgStat_KindInfo pgstat_kind_infos[PGSTAT_NUM_KINDS] = {
.reset_timestamp_cb = pgstat_subscription_reset_timestamp_cb,
},
+ [PGSTAT_KIND_RESQUEUE] = {
+ .name = "resqueue",
+
+ .fixed_amount = false,
+ /* resource queues are cluster-wide objects, visible across databases */
+ .accessed_across_databases = true,
+
+ .shared_size = sizeof(PgStatShared_ResQueue),
+ .shared_data_off = offsetof(PgStatShared_ResQueue, stats),
+ .shared_data_len = sizeof(((PgStatShared_ResQueue *) 0)->stats),
+ .pending_size = sizeof(PgStat_ResQueueCounts),
+
+ .flush_pending_cb = pgstat_resqueue_flush_cb,
+ .reset_timestamp_cb = pgstat_resqueue_reset_timestamp_cb,
+ },
+
/* stats for fixed-numbered (mostly 1) objects */
diff --git a/src/backend/utils/activity/pgstat_resqueue.c b/src/backend/utils/activity/pgstat_resqueue.c
new file mode 100644
index 00000000000..0ca7f3a94f6
--- /dev/null
+++ b/src/backend/utils/activity/pgstat_resqueue.c
@@ -0,0 +1,388 @@
+/* -------------------------------------------------------------------------
+ *
+ * pgstat_resqueue.c
+ * Implementation of resource queue statistics.
+ *
+ * Each backend maintains a hash table (keyed by portalid) of
+ * PgStat_ResQueuePortalEntry structs for in-flight portals. When a portal
+ * finishes (admitted, rejected, or completed), its timing deltas are
+ * accumulated into per-queue PgStat_ResQueueCounts pending data, which is
+ * eventually flushed into the shared-memory PgStatShared_ResQueue entry by
+ * pgstat_report_stat().
+ *
+ * Time is tracked at second granularity (via time()) to keep overhead low.
+ *
+ * Portions Copyright (c) 2006-2010, Greenplum inc.
+ * Portions Copyright (c) 2012-Present VMware, Inc. or its affiliates.
+ * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/backend/utils/activity/pgstat_resqueue.c
+ * -------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <time.h>
+
+#include "pgstat.h"
+#include "utils/hsearch.h"
+#include "utils/memutils.h"
+#include "utils/pgstat_internal.h"
+
+
+/* ----------
+ * Backend-local hash table of in-flight portal entries.
+ * Keyed by portalid (uint32).
+ * ----------
+ */
+static HTAB *pgStatResQueuePortalHash = NULL;
+
+
+/* ----------
+ * pgstat_resqueue_portal_hash_init
+ *
+ * Lazily initialise the backend-local portal tracking hash.
+ * ----------
+ */
+static void
+pgstat_resqueue_portal_hash_init(void)
+{
+ HASHCTL ctl;
+
+ if (pgStatResQueuePortalHash != NULL)
+ return;
+
+ MemSet(&ctl, 0, sizeof(ctl));
+ ctl.keysize = sizeof(uint32);
+ ctl.entrysize = sizeof(PgStat_ResQueuePortalEntry);
+ ctl.hcxt = TopMemoryContext;
+
+ pgStatResQueuePortalHash = hash_create("ResQueue portal stats",
+ 16,
+ &ctl,
+ HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+}
+
+/* ----------
+ * pgstat_resqueue_wait_start
+ *
+ * Called just before ResLockAcquire() when a portal is about to enter the
+ * resource queue. Records the wait-start timestamp and resource parameters,
+ * and immediately writes the submission counter to shared stats.
+ *
+ * We write directly to shared stats (bypassing the pending mechanism) so
+ * that stats are immediately visible to other sessions without requiring an
+ * explicit flush. This also keeps the code safe regardless of the calling
+ * context.
+ * ----------
+ */
+void
+pgstat_resqueue_wait_start(uint32 portalid, Oid queueid,
+ Cost query_cost, int64 query_memory_kb)
+{
+ PgStat_ResQueuePortalEntry *entry;
+ bool found;
+ PgStat_EntryRef *entry_ref;
+ PgStatShared_ResQueue *shqent;
+
+ /* Skip stat update if pgstat shared memory is already detached. */
+ if (pgStatLocal.shared_hash == NULL)
+ return;
+
+ pgstat_resqueue_portal_hash_init();
+
+ entry = (PgStat_ResQueuePortalEntry *)
+ hash_search(pgStatResQueuePortalHash, &portalid, HASH_ENTER, &found);
+
+ /* If a stale entry exists (e.g. from a prior run), overwrite it. */
+ entry->portalid = portalid;
+ entry->queueid = queueid;
+ entry->t_wait_start = time(NULL);
+ entry->t_exec_start = 0;
+ entry->query_cost = query_cost;
+ entry->query_memory_kb = query_memory_kb;
+
+ /* Write submission counters directly to shared stats. */
+ entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RESQUEUE,
+ InvalidOid, queueid, false);
+ if (entry_ref == NULL)
+ return;
+
+ shqent = (PgStatShared_ResQueue *) entry_ref->shared_stats;
+ shqent->stats.queries_submitted++;
+ shqent->stats.total_cost += (PgStat_Counter) query_cost;
+ shqent->stats.total_memory_kb += query_memory_kb;
+ pgstat_unlock_entry(entry_ref);
+}
+
+/* ----------
+ * pgstat_resqueue_wait_end
+ *
+ * Called after ResLockAcquire() returns successfully (portal admitted).
+ * Records the exec-start timestamp and counts the admission directly in
+ * shared stats.
+ * ----------
+ */
+void
+pgstat_resqueue_wait_end(uint32 portalid)
+{
+ PgStat_ResQueuePortalEntry *entry;
+ bool found;
+ time_t now;
+ time_t wait_secs;
+ PgStat_EntryRef *entry_ref;
+ PgStatShared_ResQueue *shqent;
+
+ if (pgStatResQueuePortalHash == NULL)
+ return;
+
+ entry = (PgStat_ResQueuePortalEntry *)
+ hash_search(pgStatResQueuePortalHash, &portalid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ now = time(NULL);
+ entry->t_exec_start = now;
+
+ wait_secs = (entry->t_wait_start > 0) ? (now - entry->t_wait_start) : 0;
+ if (wait_secs < 0)
+ wait_secs = 0;
+
+ entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RESQUEUE,
+ InvalidOid, entry->queueid, false);
+ if (entry_ref == NULL)
+ return;
+
+ shqent = (PgStatShared_ResQueue *) entry_ref->shared_stats;
+ shqent->stats.queries_admitted++;
+ shqent->stats.elapsed_wait_secs += (PgStat_Counter) wait_secs;
+ if ((PgStat_Counter) wait_secs > shqent->stats.max_wait_secs)
+ shqent->stats.max_wait_secs = (PgStat_Counter) wait_secs;
+ pgstat_unlock_entry(entry_ref);
+}
+
+/* ----------
+ * pgstat_resqueue_rejected
+ *
+ * Called when ResLockAcquire() raises an error (portal cancelled or error
+ * while waiting), or when the portal is bypassed (cost below threshold).
+ * Removes the portal entry without counting exec time.
+ *
+ * IMPORTANT: This function may be called from inside a PG_CATCH block.
+ * It must NOT call pgstat_prep_pending_entry(), which modifies the global
+ * pgStatPending dlist and allocates memory that may be unsafe to use during
+ * error recovery. Instead, we update shared stats directly via
+ * pgstat_get_entry_ref_locked(), which is PG_CATCH-safe because it only
+ * allocates from TopMemoryContext derivatives and uses LWLock operations.
+ * ----------
+ */
+void
+pgstat_resqueue_rejected(uint32 portalid)
+{
+ PgStat_ResQueuePortalEntry *entry;
+ bool found;
+ time_t now;
+ time_t wait_secs;
+ Oid queueid;
+ PgStat_EntryRef *entry_ref;
+ PgStatShared_ResQueue *shqent;
+
+ if (pgStatResQueuePortalHash == NULL)
+ return;
+
+ entry = (PgStat_ResQueuePortalEntry *)
+ hash_search(pgStatResQueuePortalHash, &portalid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ now = time(NULL);
+ wait_secs = (entry->t_wait_start > 0) ? (now - entry->t_wait_start) : 0;
+ if (wait_secs < 0)
+ wait_secs = 0;
+
+ queueid = entry->queueid;
+
+ /* Remove portal entry first — hash_search(HASH_REMOVE) is PG_CATCH-safe. */
+ hash_search(pgStatResQueuePortalHash, &portalid, HASH_REMOVE, NULL);
+
+ /* Skip stat update if pgstat shared memory is already detached. */
+ if (pgStatLocal.shared_hash == NULL)
+ return;
+
+ /*
+ * Update the shared stats entry directly, bypassing the pending
+ * mechanism. pgstat_get_entry_ref_locked allocates only from
+ * TopMemoryContext derivatives and takes an LWLock, both of which are
+ * safe during error recovery.
+ */
+ entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RESQUEUE,
+ InvalidOid, queueid, false);
+ if (entry_ref == NULL)
+ return;
+
+ shqent = (PgStatShared_ResQueue *) entry_ref->shared_stats;
+ shqent->stats.queries_rejected++;
+ shqent->stats.elapsed_wait_secs += (PgStat_Counter) wait_secs;
+ if ((PgStat_Counter) wait_secs > shqent->stats.max_wait_secs)
+ shqent->stats.max_wait_secs = (PgStat_Counter) wait_secs;
+
+ pgstat_unlock_entry(entry_ref);
+}
+
+/* ----------
+ * pgstat_resqueue_exec_end
+ *
+ * Called from ResUnLockPortal() when a portal finishes execution (normal
+ * completion, error, or cancel after admission). Writes completion counters
+ * directly to shared stats.
+ * ----------
+ */
+void
+pgstat_resqueue_exec_end(uint32 portalid)
+{
+ PgStat_ResQueuePortalEntry *entry;
+ bool found;
+ time_t now;
+ time_t exec_secs;
+ PgStat_EntryRef *entry_ref;
+ PgStatShared_ResQueue *shqent;
+
+ if (pgStatResQueuePortalHash == NULL)
+ return;
+
+ /*
+ * pgstat_shutdown_hook (before_shmem_exit) runs before ProcKill
+ * (on_shmem_exit). If AtExitCleanup_ResPortals calls us after pgstat
+ * has detached from shared memory, skip the stat update but still clean
+ * up the local hash entry to avoid a memory leak.
+ */
+ if (pgStatLocal.shared_hash == NULL)
+ {
+ hash_search(pgStatResQueuePortalHash, &portalid, HASH_REMOVE, NULL);
+ return;
+ }
+
+ entry = (PgStat_ResQueuePortalEntry *)
+ hash_search(pgStatResQueuePortalHash, &portalid, HASH_FIND, &found);
+
+ if (!found)
+ return;
+
+ now = time(NULL);
+ exec_secs = (entry->t_exec_start > 0) ? (now - entry->t_exec_start) : 0;
+ if (exec_secs < 0)
+ exec_secs = 0;
+
+ entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RESQUEUE,
+ InvalidOid, entry->queueid, false);
+ if (entry_ref != NULL)
+ {
+ shqent = (PgStatShared_ResQueue *) entry_ref->shared_stats;
+ shqent->stats.queries_completed++;
+ shqent->stats.elapsed_exec_secs += (PgStat_Counter) exec_secs;
+ if ((PgStat_Counter) exec_secs > shqent->stats.max_exec_secs)
+ shqent->stats.max_exec_secs = (PgStat_Counter) exec_secs;
+ pgstat_unlock_entry(entry_ref);
+ }
+
+ hash_search(pgStatResQueuePortalHash, &portalid, HASH_REMOVE, NULL);
+}
+
+/* ----------
+ * pgstat_create_resqueue
+ *
+ * Called when a resource queue is created via DDL. Ensures a stats entry
+ * exists and is initialised.
+ * ----------
+ */
+void
+pgstat_create_resqueue(Oid queueid)
+{
+ pgstat_create_transactional(PGSTAT_KIND_RESQUEUE, InvalidOid, queueid);
+ pgstat_get_entry_ref(PGSTAT_KIND_RESQUEUE, InvalidOid, queueid, true, NULL);
+ pgstat_reset_entry(PGSTAT_KIND_RESQUEUE, InvalidOid, queueid, 0);
+}
+
+/* ----------
+ * pgstat_drop_resqueue
+ *
+ * Called when a resource queue is dropped via DDL.
+ * ----------
+ */
+void
+pgstat_drop_resqueue(Oid queueid)
+{
+ pgstat_drop_transactional(PGSTAT_KIND_RESQUEUE, InvalidOid, queueid);
+}
+
+/* ----------
+ * pgstat_fetch_stat_resqueue
+ *
+ * Return a palloc'd snapshot of statistics for the given resource queue OID,
+ * or NULL if no stats entry exists.
+ * ----------
+ */
+PgStat_StatResQueueEntry *
+pgstat_fetch_stat_resqueue(Oid queueid)
+{
+ return (PgStat_StatResQueueEntry *)
+ pgstat_fetch_entry(PGSTAT_KIND_RESQUEUE, InvalidOid, queueid);
+}
+
+/* ----------
+ * pgstat_resqueue_flush_cb
+ *
+ * Flush pending per-queue delta counters into shared memory.
+ * Called by pgstat_report_stat() for each entry with pending data.
+ *
+ * max_wait_secs and max_exec_secs are merged with MAX rather than addition.
+ * ----------
+ */
+bool
+pgstat_resqueue_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
+{
+ PgStat_ResQueueCounts *localent;
+ PgStatShared_ResQueue *shqent;
+
+ localent = (PgStat_ResQueueCounts *) entry_ref->pending;
+ shqent = (PgStatShared_ResQueue *) entry_ref->shared_stats;
+
+ if (!pgstat_lock_entry(entry_ref, nowait))
+ return false;
+
+#define RESQUEUE_ACC(fld) shqent->stats.fld += localent->fld
+ RESQUEUE_ACC(queries_submitted);
+ RESQUEUE_ACC(queries_admitted);
+ RESQUEUE_ACC(queries_rejected);
+ RESQUEUE_ACC(queries_completed);
+ RESQUEUE_ACC(elapsed_wait_secs);
+ RESQUEUE_ACC(elapsed_exec_secs);
+ RESQUEUE_ACC(total_cost);
+ RESQUEUE_ACC(total_memory_kb);
+#undef RESQUEUE_ACC
+
+ /* max fields: merge with MAX */
+ if (localent->max_wait_secs > shqent->stats.max_wait_secs)
+ shqent->stats.max_wait_secs = localent->max_wait_secs;
+ if (localent->max_exec_secs > shqent->stats.max_exec_secs)
+ shqent->stats.max_exec_secs = localent->max_exec_secs;
+
+ pgstat_unlock_entry(entry_ref);
+
+ return true;
+}
+
+/* ----------
+ * pgstat_resqueue_reset_timestamp_cb
+ *
+ * Reset the stat_reset_timestamp in the shared entry.
+ * ----------
+ */
+void
+pgstat_resqueue_reset_timestamp_cb(PgStatShared_Common *header, TimestampTz ts)
+{
+ ((PgStatShared_ResQueue *) header)->stats.stat_reset_timestamp = ts;
+}
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index aeffe94a39e..61c69b3ca4e 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -2116,6 +2116,84 @@ pg_stat_get_subscription_stats(PG_FUNCTION_ARGS)
PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
}
+/*
+ * pg_stat_get_resqueue_stats
+ *
+ * Returns cumulative statistics for one resource queue as a composite row.
+ * Returns all-zeros if no stats entry exists for the given queue OID.
+ */
+Datum
+pg_stat_get_resqueue_stats(PG_FUNCTION_ARGS)
+{
+#define PG_STAT_GET_RESQUEUE_STATS_COLS 13
+ Oid queueid = PG_GETARG_OID(0);
+ TupleDesc tupdesc;
+ Datum values[PG_STAT_GET_RESQUEUE_STATS_COLS] = {0};
+ bool nulls[PG_STAT_GET_RESQUEUE_STATS_COLS] = {0};
+ PgStat_StatResQueueEntry *entry;
+ PgStat_StatResQueueEntry allzero;
+
+ /* Fetch stats; fall back to all-zero if queue has no stats yet */
+ entry = pgstat_fetch_stat_resqueue(queueid);
+ if (!entry)
+ {
+ memset(&allzero, 0, sizeof(PgStat_StatResQueueEntry));
+ entry = &allzero;
+ }
+
+ tupdesc = CreateTemplateTupleDesc(PG_STAT_GET_RESQUEUE_STATS_COLS);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 1, "queueid",
+ OIDOID, -1, 0);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 2, "queries_submitted",
+ INT8OID, -1, 0);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 3, "queries_admitted",
+ INT8OID, -1, 0);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 4, "queries_rejected",
+ INT8OID, -1, 0);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 5, "queries_completed",
+ INT8OID, -1, 0);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 6, "elapsed_wait_secs",
+ INT8OID, -1, 0);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 7, "max_wait_secs",
+ INT8OID, -1, 0);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 8, "elapsed_exec_secs",
+ INT8OID, -1, 0);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 9, "max_exec_secs",
+ INT8OID, -1, 0);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 10, "total_cost",
+ INT8OID, -1, 0);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 11, "total_memory_kb",
+ INT8OID, -1, 0);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 12, "stat_reset_timestamp",
+ TIMESTAMPTZOID, -1, 0);
+ TupleDescInitEntry(tupdesc, (AttrNumber) 13, "have_stats",
+ BOOLOID, -1, 0);
+ BlessTupleDesc(tupdesc);
+
+ values[0] = ObjectIdGetDatum(queueid);
+ values[1] = Int64GetDatum(entry->queries_submitted);
+ values[2] = Int64GetDatum(entry->queries_admitted);
+ values[3] = Int64GetDatum(entry->queries_rejected);
+ values[4] = Int64GetDatum(entry->queries_completed);
+ values[5] = Int64GetDatum(entry->elapsed_wait_secs);
+ values[6] = Int64GetDatum(entry->max_wait_secs);
+ values[7] = Int64GetDatum(entry->elapsed_exec_secs);
+ values[8] = Int64GetDatum(entry->max_exec_secs);
+ values[9] = Int64GetDatum(entry->total_cost);
+ values[10] = Int64GetDatum(entry->total_memory_kb);
+
+ if (entry->stat_reset_timestamp == 0)
+ nulls[11] = true;
+ else
+ values[11] = TimestampTzGetDatum(entry->stat_reset_timestamp);
+
+ /* have_stats: true when an actual stats entry was found */
+ values[12] = BoolGetDatum(pgstat_have_entry(PGSTAT_KIND_RESQUEUE,
+ InvalidOid, queueid));
+
+ PG_RETURN_DATUM(HeapTupleGetDatum(heap_form_tuple(tupdesc, values, nulls)));
+}
+
/*
* Checks for presence of stats for object with provided kind, database oid,
* object oid.
diff --git a/src/backend/utils/resscheduler/resqueue.c b/src/backend/utils/resscheduler/resqueue.c
index 3af903f089a..5cb7d9bffa1 100644
--- a/src/backend/utils/resscheduler/resqueue.c
+++ b/src/backend/utils/resscheduler/resqueue.c
@@ -1367,8 +1367,7 @@ ResProcLockRemoveSelfAndWakeup(LOCK *lock)
nextproc = (PGPROC *) proc->links.next;
- dclist_delete_from(waitQueue, &(proc->links));
- (proc->waitLock->waitProcs.count)--;
+ dclist_delete_from_thoroughly(waitQueue, &(proc->links));
proc = nextproc;
@@ -1472,8 +1471,7 @@ ResProcWakeup(PGPROC *proc, int waitStatus)
retProc = (PGPROC *) proc->links.next;
/* Remove process from wait queue */
- dclist_delete_from(&proc->waitLock->waitProcs, &(proc->links));
- (proc->waitLock->waitProcs.count)--;
+ dclist_delete_from_thoroughly(&proc->waitLock->waitProcs, &(proc->links));
/* Clean up process' state and pass it the ok/fail signal */
proc->waitLock = NULL;
@@ -1511,8 +1509,7 @@ ResRemoveFromWaitQueue(PGPROC *proc, uint32 hashcode)
Assert(waitLock->waitProcs.count > 0);
/* Remove proc from lock's wait queue */
- dclist_delete_from(&waitLock->waitProcs, &(proc->links));
- waitLock->waitProcs.count--;
+ dclist_delete_from_thoroughly(&waitLock->waitProcs, &(proc->links));
/* Undo increments of request counts by waiting process */
Assert(waitLock->nRequested > 0);
diff --git a/src/backend/utils/resscheduler/resscheduler.c b/src/backend/utils/resscheduler/resscheduler.c
index 375982b3015..5a09d5541eb 100644
--- a/src/backend/utils/resscheduler/resscheduler.c
+++ b/src/backend/utils/resscheduler/resscheduler.c
@@ -677,28 +677,35 @@ ResLockPortal(Portal portal, QueryDesc *qDesc)
if (takeLock)
{
#ifdef RESLOCK_DEBUG
- elog(DEBUG1, "acquire resource lock for queue %u (portal %u)",
+ elog(DEBUG1, "acquire resource lock for queue %u (portal %u)",
queueid, portal->portalId);
#endif
SET_LOCKTAG_RESOURCE_QUEUE(tag, queueid);
+ /* Record wait-start and submission for pgstat tracking. */
+ pgstat_resqueue_wait_start(portal->portalId, queueid,
+ incData.increments[RES_COST_LIMIT],
+ (int64) (incData.increments[RES_MEMORY_LIMIT] / 1024));
+
PG_TRY();
{
lockResult = ResLockAcquire(&tag, &incData);
}
PG_CATCH();
{
- /*
- * We might have been waiting for a resource queue lock when we get
- * here. Calling ResLockRelease without calling ResLockWaitCancel will
+ /*
+ * We might have been waiting for a resource queue lock when we get
+ * here. Calling ResLockRelease without calling ResLockWaitCancel will
* cause the locallock to be cleaned up, but will leave the global
- * variable lockAwaited still pointing to the locallock hash
+ * variable lockAwaited still pointing to the locallock hash
* entry.
*/
ResLockWaitCancel();
-
- /* If we had acquired the resource queue lock, release it and clean up */
+ /* Count this portal as rejected in pgstat. */
+ pgstat_resqueue_rejected(portal->portalId);
+
+ /* If we had acquired the resource queue lock, release it and clean up */
if (!ResLockRelease(&tag, portal->portalId))
{
ereport(LOG,
@@ -709,7 +716,7 @@ ResLockPortal(Portal portal, QueryDesc *qDesc)
tag.locktag_field1, portal->portalId,
portal->name, portal->sourceText)));
}
-
+
/* GPDB hook for collecting query info */
if (query_info_collect_hook)
(*query_info_collect_hook)(METRICS_QUERY_ERROR, qDesc);
@@ -721,17 +728,24 @@ ResLockPortal(Portal portal, QueryDesc *qDesc)
}
PG_END_TRY();
- /*
+ /*
* See if query was too small to bother locking at all, i.e had
* cost smaller than the ignore cost threshold for the queue.
*/
if (lockResult == LOCKACQUIRE_NOT_AVAIL)
{
#ifdef RESLOCK_DEBUG
- elog(DEBUG1, "cancel resource lock for queue %u (portal %u)",
+ elog(DEBUG1, "cancel resource lock for queue %u (portal %u)",
queueid, portal->portalId);
#endif
- /*
+ /*
+ * Query cost was below the ignore threshold; the portal won't
+ * consume a queue slot. Remove the pgstat portal entry we
+ * created above without counting it as admitted.
+ */
+ pgstat_resqueue_rejected(portal->portalId);
+
+ /*
* Reset portalId and queueid for this portal so the queue
* and increment accounting tests continue to work properly.
*/
@@ -739,6 +753,11 @@ ResLockPortal(Portal portal, QueryDesc *qDesc)
portal->portalId = INVALID_PORTALID;
shouldReleaseLock = false;
}
+ else
+ {
+ /* Portal was admitted into the queue; record exec-start time. */
+ pgstat_resqueue_wait_end(portal->portalId);
+ }
/* Count holdable cursors (if we are locking this one) .*/
if (portal->cursorOptions & CURSOR_OPT_HOLD && shouldReleaseLock)
@@ -789,6 +808,11 @@ ResLockUtilityPortal(Portal portal, float4 ignoreCostLimit)
#endif
SET_LOCKTAG_RESOURCE_QUEUE(tag, queueid);
+ /* Record wait-start for utility statement. */
+ pgstat_resqueue_wait_start(portal->portalId, queueid,
+ (Cost) incData.increments[RES_COST_LIMIT],
+ 0 /* no memory tracking for utility stmts */);
+
PG_TRY();
{
lockResult = ResLockAcquire(&tag, &incData);
@@ -804,6 +828,9 @@ ResLockUtilityPortal(Portal portal, float4 ignoreCostLimit)
*/
ResLockWaitCancel();
+ /* Count this portal as rejected in pgstat. */
+ pgstat_resqueue_rejected(portal->portalId);
+
/* If we had acquired the resource queue lock, release it and clean up */
if (!ResLockRelease(&tag, portal->portalId))
{
@@ -826,8 +853,13 @@ ResLockUtilityPortal(Portal portal, float4 ignoreCostLimit)
PG_RE_THROW();
}
PG_END_TRY();
+
+ if (lockResult != LOCKACQUIRE_NOT_AVAIL)
+ pgstat_resqueue_wait_end(portal->portalId);
+ else
+ pgstat_resqueue_rejected(portal->portalId);
}
-
+
portal->hasResQueueLock = shouldReleaseLock;
}
@@ -842,15 +874,19 @@ ResUnLockPortal(Portal portal)
queueid = portal->queueId;
- /*
+ /*
* Check we have a valid queue before going any further.
*/
if (queueid != InvalidOid)
{
#ifdef RESLOCK_DEBUG
- elog(DEBUG1, "release resource lock for queue %u (portal %u)",
+ elog(DEBUG1, "release resource lock for queue %u (portal %u)",
queueid, portal->portalId);
#endif
+
+ /* Record execution completion in pgstat. */
+ pgstat_resqueue_exec_end(portal->portalId);
+
SET_LOCKTAG_RESOURCE_QUEUE(tag, queueid);
if (!ResLockRelease(&tag, portal->portalId))
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 3b1f3a7e327..f788bd349e4 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -13005,3 +13005,11 @@
{ oid => 8693, descr => 'deparse SCHEDULE clause for a given dynamic table',
proname => 'pg_get_dynamic_table_schedule', provolatile => 's', prorettype => 'text',
proargtypes => 'oid', prosrc => 'pg_get_dynamic_table_schedule' },
+
+{ oid => '9200', descr => 'statistics: cumulative statistics for a resource queue',
+ proname => 'pg_stat_get_resqueue_stats', provolatile => 's',
+ proparallel => 'r', prorettype => 'record', proargtypes => 'oid',
+ proallargtypes => '{oid,oid,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,timestamptz,bool}',
+ proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+ proargnames => '{queueid,queueid,queries_submitted,queries_admitted,queries_rejected,queries_completed,elapsed_wait_secs,max_wait_secs,elapsed_exec_secs,max_exec_secs,total_cost,total_memory_kb,stat_reset_timestamp,have_stats}',
+ prosrc => 'pg_stat_get_resqueue_stats' },
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 338f5ae9562..c4f3b88444a 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -45,6 +45,7 @@ typedef enum PgStat_Kind
PGSTAT_KIND_FUNCTION, /* per-function statistics */
PGSTAT_KIND_REPLSLOT, /* per-slot statistics */
PGSTAT_KIND_SUBSCRIPTION, /* per-subscription statistics */
+ PGSTAT_KIND_RESQUEUE, /* per-resource-queue statistics */
/* stats for fixed-numbered objects */
PGSTAT_KIND_ARCHIVER,
@@ -237,7 +238,7 @@ typedef struct PgStat_TableXactStatus
* ------------------------------------------------------------
*/
-#define PGSTAT_FILE_FORMAT_ID 0x01A5BCAC
+#define PGSTAT_FILE_FORMAT_ID 0x01A5BCAD
typedef struct PgStat_ArchiverStats
{
@@ -457,6 +458,63 @@ typedef struct PgStat_PendingWalStats
} PgStat_PendingWalStats;
+/* ----------
+ * PgStat_StatResQueueEntry
+ *
+ * Per-resource-queue cumulative statistics, stored in shared memory and
+ * persisted to disk. Exposed via the pg_stat_resqueues view.
+ *
+ * Time values are in seconds (matching time() granularity used during
+ * portal tracking). max_wait_secs and max_exec_secs are historical peaks.
+ * ----------
+ */
+typedef struct PgStat_StatResQueueEntry
+{
+ /* throughput counters */
+ PgStat_Counter queries_submitted; /* total queries entered the queue */
+ PgStat_Counter queries_admitted; /* total queries admitted from queue */
+ PgStat_Counter queries_rejected; /* queries cancelled/errored while waiting */
+ PgStat_Counter queries_completed; /* queries finished execution */
+
+ /* wait time (seconds) */
+ PgStat_Counter elapsed_wait_secs; /* cumulative wait seconds */
+ PgStat_Counter max_wait_secs; /* peak single-query wait time */
+
+ /* exec time (seconds) */
+ PgStat_Counter elapsed_exec_secs; /* cumulative exec seconds */
+ PgStat_Counter max_exec_secs; /* peak single-query exec time */
+
+ /* resource usage */
+ PgStat_Counter total_cost; /* cumulative planner cost estimate */
+ PgStat_Counter total_memory_kb; /* cumulative memory granted (KB) */
+
+ TimestampTz stat_reset_timestamp;
+} PgStat_StatResQueueEntry;
+
+/* ----------
+ * PgStat_ResQueueCounts
+ *
+ * Pending (not-yet-flushed) per-resource-queue delta counters accumulated by
+ * a single backend. Flushed into PgStat_StatResQueueEntry in shared memory
+ * during pgstat_report_stat().
+ *
+ * This struct must contain only delta counters so that memcmp against zeroes
+ * reliably detects whether there are pending updates.
+ * ----------
+ */
+typedef struct PgStat_ResQueueCounts
+{
+ PgStat_Counter queries_submitted;
+ PgStat_Counter queries_admitted;
+ PgStat_Counter queries_rejected;
+ PgStat_Counter queries_completed;
+ PgStat_Counter elapsed_wait_secs;
+ PgStat_Counter elapsed_exec_secs;
+ PgStat_Counter max_wait_secs; /* max in this flush batch */
+ PgStat_Counter max_exec_secs; /* max in this flush batch */
+ PgStat_Counter total_cost;
+ PgStat_Counter total_memory_kb;
+} PgStat_ResQueueCounts;
/*
@@ -702,6 +760,20 @@ extern void pgstat_drop_subscription(Oid subid);
extern PgStat_StatSubEntry *pgstat_fetch_stat_subscription(Oid subid);
+/*
+ * Functions in pgstat_resqueue.c
+ */
+
+extern void pgstat_resqueue_wait_start(uint32 portalid, Oid queueid,
+ Cost query_cost, int64 query_memory_kb);
+extern void pgstat_resqueue_wait_end(uint32 portalid);
+extern void pgstat_resqueue_rejected(uint32 portalid);
+extern void pgstat_resqueue_exec_end(uint32 portalid);
+extern void pgstat_create_resqueue(Oid queueid);
+extern void pgstat_drop_resqueue(Oid queueid);
+extern PgStat_StatResQueueEntry *pgstat_fetch_stat_resqueue(Oid queueid);
+
+
/*
* Functions in pgstat_xact.c
*/
diff --git a/src/include/utils/pgstat_internal.h b/src/include/utils/pgstat_internal.h
index f886ab7f4bc..2b3da610a31 100644
--- a/src/include/utils/pgstat_internal.h
+++ b/src/include/utils/pgstat_internal.h
@@ -413,6 +413,37 @@ typedef struct PgStatShared_ReplSlot
PgStat_StatReplSlotEntry stats;
} PgStatShared_ReplSlot;
+typedef struct PgStatShared_ResQueue
+{
+ PgStatShared_Common header;
+ PgStat_StatResQueueEntry stats;
+} PgStatShared_ResQueue;
+
+/* ----------
+ * PgStat_ResQueuePortalEntry
+ *
+ * Backend-local tracking entry for a single portal subject to resource queue
+ * scheduling. Records timestamps and resource info for a portal's lifetime,
+ * then rolls them into per-queue PgStat_ResQueueCounts when the portal exits.
+ *
+ * The collector never sees this struct.
+ * ----------
+ */
+typedef struct PgStat_ResQueuePortalEntry
+{
+ uint32 portalid; /* hash key */
+ Oid queueid; /* resource queue this portal belongs to */
+ time_t t_wait_start; /* time portal started waiting in queue */
+ time_t t_exec_start; /* time portal was admitted & began executing */
+ Cost query_cost; /* planner cost estimate */
+ int64 query_memory_kb; /* memory granted (KB) */
+} PgStat_ResQueuePortalEntry;
+
+/* Callbacks for pgstat_kind_infos registration (used in pgstat.c). */
+extern bool pgstat_resqueue_flush_cb(PgStat_EntryRef *entry_ref, bool nowait);
+extern void pgstat_resqueue_reset_timestamp_cb(PgStatShared_Common *header,
+ TimestampTz ts);
+
/*
* Central shared memory entry for the cumulative stats system.
diff --git a/src/test/isolation2/expected/resource_queue_deadlock.out b/src/test/isolation2/expected/resource_queue_deadlock.out
index 06309f34b6d..3c14d72ba4d 100644
--- a/src/test/isolation2/expected/resource_queue_deadlock.out
+++ b/src/test/isolation2/expected/resource_queue_deadlock.out
@@ -5,6 +5,8 @@
CREATE
0: CREATE role role_deadlock_test RESOURCE QUEUE rq_deadlock_test;
CREATE
+0: GRANT CREATE ON SCHEMA public TO role_deadlock_test;
+GRANT
0: SET gp_autostats_lock_wait TO ON;
SET
@@ -50,8 +52,8 @@ ANALYZE
(1 row)
2: SELECT * FROM t_deadlock_test;
ERROR: deadlock detected
-DETAIL: Process 1618 waits for ExclusiveLock on resource queue 16520; blocked by process 1606.
-Process 1606 waits for ShareUpdateExclusiveLock on relation 16522 of database 16478; blocked by process 1618.
+Process PID waits for ExclusiveLock on resource queue OID; blocked by process PID.
+Process PID waits for ShareUpdateExclusiveLock on relation OID of database OID; blocked by process PID.
HINT: See server log for query details.
2: ROLLBACK;
ROLLBACK
@@ -68,6 +70,8 @@ INSERT 1
-- Clean up the test
0: DROP TABLE t_deadlock_test;
DROP
+0: REVOKE CREATE ON SCHEMA public FROM role_deadlock_test;
+REVOKE
0: DROP ROLE role_deadlock_test;
DROP
0: DROP RESOURCE QUEUE rq_deadlock_test;
diff --git a/src/test/isolation2/expected/resource_queue_multi_portal.out b/src/test/isolation2/expected/resource_queue_multi_portal.out
index 5fe141ccc9c..8070edc230f 100644
--- a/src/test/isolation2/expected/resource_queue_multi_portal.out
+++ b/src/test/isolation2/expected/resource_queue_multi_portal.out
@@ -100,8 +100,8 @@ DECLARE
-- After ending the transactions, there should be 0 active statements.
1<: <... completed>
ERROR: deadlock detected
-DETAIL: Process 738539 waits for ExclusiveLock on resource queue 90366; blocked by process 738548.
-Process 738548 waits for ExclusiveLock on resource queue 90366; blocked by process 738539.
+Process PID waits for ExclusiveLock on resource queue OID; blocked by process PID.
+Process PID waits for ExclusiveLock on resource queue OID; blocked by process PID.
HINT: See server log for query details.
1:END;
END
@@ -135,7 +135,7 @@ DECLARE
-- its transaction.
1:DECLARE c3 CURSOR FOR SELECT 1;
ERROR: deadlock detected, locking against self
-DETAIL: resource queue id: 585193, portal id: 3
+DETAIL: resource queue id: OID, portal id: NUM
-- There should be 0 active statements following the transaction abort.
0:SELECT rsqcountlimit, rsqcountvalue FROM pg_resqueue_status WHERE rsqname = 'rq_multi_portal';
diff --git a/src/test/isolation2/expected/resqueue_stats.out b/src/test/isolation2/expected/resqueue_stats.out
new file mode 100644
index 00000000000..9bf02f77060
--- /dev/null
+++ b/src/test/isolation2/expected/resqueue_stats.out
@@ -0,0 +1,92 @@
+-- Test pg_stat_resqueues cumulative statistics for resource queues.
+
+0:CREATE RESOURCE QUEUE rq_stats_test WITH (active_statements = 1);
+CREATE
+0:CREATE ROLE role_stats_test RESOURCE QUEUE rq_stats_test;
+CREATE
+
+-- Session 1 holds the queue slot so session 2 will block.
+1:SET role role_stats_test;
+SET
+1:BEGIN;
+BEGIN
+1:DECLARE c1 CURSOR FOR SELECT 1;
+DECLARE
+
+-- Session 2 submits a query that will block.
+2:SET role role_stats_test;
+SET
+2&:SELECT pg_sleep(0); <waiting ...>
+
+-- Verify session 2 is waiting on the resource queue.
+0:SELECT wait_event_type, wait_event FROM pg_stat_activity WHERE query = 'SELECT pg_sleep(0);';
+ wait_event_type | wait_event
+-----------------+---------------
+ ResourceQueue | ResourceQueue
+(1 row)
+
+-- Cancel the blocked query (increments queries_rejected).
+0:SELECT pg_cancel_backend(pid) FROM pg_stat_activity WHERE query = 'SELECT pg_sleep(0);' AND wait_event = 'ResourceQueue';
+ pg_cancel_backend
+-------------------
+ t
+(1 row)
+
+2<: <... completed>
+ERROR: canceling statement due to user request
+
+-- Release session 1's slot so later sessions can proceed.
+1:CLOSE c1;
+CLOSE
+1:END;
+END
+
+-- Session 3 runs a query that should be admitted and complete normally.
+3:SET role role_stats_test;
+SET
+3:SELECT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+-- Session 4 runs another query that completes normally.
+4:SET role role_stats_test;
+SET
+4:SELECT 2;
+ ?column?
+----------
+ 2
+(1 row)
+
+-- All resqueue stats are written directly to shared memory (no flush needed).
+-- Check that the view shows the expected minimum counts.
+-- queries_submitted >= 3: sessions 2 (rejected), 3, 4
+-- queries_admitted >= 2: sessions 3 and 4
+-- queries_completed >= 2: sessions 3 and 4
+0:SELECT queuename, queries_submitted >= 3 AS submitted_ok, queries_admitted >= 2 AS admitted_ok, queries_completed >= 2 AS completed_ok FROM pg_stat_resqueues WHERE queuename = 'rq_stats_test';
+ queuename | submitted_ok | admitted_ok | completed_ok
+---------------+--------------+-------------+--------------
+ rq_stats_test | t | t | t
+(1 row)
+
+-- All counter columns must be non-negative.
+0:SELECT queries_submitted >= 0 AS sub_nn, queries_admitted >= 0 AS adm_nn, queries_rejected >= 0 AS rej_nn, queries_completed >= 0 AS cmp_nn, total_wait_time_secs >= 0 AS wait_nn, max_wait_secs >= 0 AS maxw_nn, total_exec_time_secs >= 0 AS exec_nn, max_exec_secs >= 0 AS maxe_nn, total_cost >= 0 AS cost_nn, total_memory_kb >= 0 AS mem_nn FROM pg_stat_resqueues WHERE queuename = 'rq_stats_test';
+ sub_nn | adm_nn | rej_nn | cmp_nn | wait_nn | maxw_nn | exec_nn | maxe_nn | cost_nn | mem_nn
+--------+--------+--------+--------+---------+---------+---------+---------+---------+--------
+ t | t | t | t | t | t | t | t | t | t
+(1 row)
+
+-- Verify pg_stat_get_resqueue_stats() returns data directly.
+-- The function has OUT parameters so no column definition list is needed.
+0:SELECT queries_submitted >= 0 AS ok FROM pg_stat_get_resqueue_stats( (SELECT oid FROM pg_resqueue WHERE rsqname = 'rq_stats_test') ) AS s;
+ ok
+------
+ t
+(1 row)
+
+-- Cleanup.
+0:DROP ROLE role_stats_test;
+DROP
+0:DROP RESOURCE QUEUE rq_stats_test;
+DROP
diff --git a/src/test/isolation2/init_file_isolation2 b/src/test/isolation2/init_file_isolation2
index 1c01246e203..c359d3b8254 100644
--- a/src/test/isolation2/init_file_isolation2
+++ b/src/test/isolation2/init_file_isolation2
@@ -46,6 +46,10 @@ s/^DETAIL: Process \d+ waits for ShareLock on transaction \d+; blocked by proce
m/.*Process \d+ waits for ExclusiveLock on resource queue \d+; blocked by process \d+./
s/.*Process \d+ waits for ExclusiveLock on resource queue \d+; blocked by process \d+./Process PID waits for ExclusiveLock on resource queue OID; blocked by process PID./
+# For resource queue self-deadlock detail
+m/resource queue id: \d+, portal id: \d+/
+s/resource queue id: \d+, portal id: \d+/resource queue id: OID, portal id: NUM/
+
m/^Process \d+ waits for ShareLock on transaction \d+; blocked by process \d+./
s/^Process \d+ waits for ShareLock on transaction \d+; blocked by process \d+./Process PID waits for ShareLock on transaction XID; blocked by process PID./
diff --git a/src/test/isolation2/sql/resource_queue_deadlock.sql b/src/test/isolation2/sql/resource_queue_deadlock.sql
index 6591e8c75b6..1b2ae432c06 100644
--- a/src/test/isolation2/sql/resource_queue_deadlock.sql
+++ b/src/test/isolation2/sql/resource_queue_deadlock.sql
@@ -3,6 +3,7 @@
0: CREATE RESOURCE QUEUE rq_deadlock_test WITH (active_statements = 1);
0: CREATE role role_deadlock_test RESOURCE QUEUE rq_deadlock_test;
+0: GRANT CREATE ON SCHEMA public TO role_deadlock_test;
0: SET gp_autostats_lock_wait TO ON;
0: SELECT gp_inject_fault_infinite('before_auto_stats', 'suspend', dbid) FROM gp_segment_configuration WHERE content = -1 AND role = 'p';
@@ -30,5 +31,6 @@
-- Clean up the test
0: DROP TABLE t_deadlock_test;
+0: REVOKE CREATE ON SCHEMA public FROM role_deadlock_test;
0: DROP ROLE role_deadlock_test;
0: DROP RESOURCE QUEUE rq_deadlock_test;
diff --git a/src/test/isolation2/sql/resqueue_stats.sql b/src/test/isolation2/sql/resqueue_stats.sql
new file mode 100644
index 00000000000..01e6041f839
--- /dev/null
+++ b/src/test/isolation2/sql/resqueue_stats.sql
@@ -0,0 +1,72 @@
+-- Test pg_stat_resqueues cumulative statistics for resource queues.
+
+0:CREATE RESOURCE QUEUE rq_stats_test WITH (active_statements = 1);
+0:CREATE ROLE role_stats_test RESOURCE QUEUE rq_stats_test;
+
+-- Session 1 holds the queue slot so session 2 will block.
+1:SET role role_stats_test;
+1:BEGIN;
+1:DECLARE c1 CURSOR FOR SELECT 1;
+
+-- Session 2 submits a query that will block.
+2:SET role role_stats_test;
+2&:SELECT pg_sleep(0);
+
+-- Verify session 2 is waiting on the resource queue.
+0:SELECT wait_event_type, wait_event FROM pg_stat_activity WHERE query = 'SELECT pg_sleep(0);';
+
+-- Cancel the blocked query (increments queries_rejected).
+0:SELECT pg_cancel_backend(pid) FROM pg_stat_activity WHERE query = 'SELECT pg_sleep(0);' AND wait_event = 'ResourceQueue';
+
+2<:
+
+-- Release session 1's slot so later sessions can proceed.
+1:CLOSE c1;
+1:END;
+
+-- Session 3 runs a query that should be admitted and complete normally.
+3:SET role role_stats_test;
+3:SELECT 1;
+
+-- Session 4 runs another query that completes normally.
+4:SET role role_stats_test;
+4:SELECT 2;
+
+-- All resqueue stats are written directly to shared memory (no flush needed).
+-- Check that the view shows the expected minimum counts.
+-- queries_submitted >= 3: sessions 2 (rejected), 3, 4
+-- queries_admitted >= 2: sessions 3 and 4
+-- queries_completed >= 2: sessions 3 and 4
+0:SELECT
+ queuename,
+ queries_submitted >= 3 AS submitted_ok,
+ queries_admitted >= 2 AS admitted_ok,
+ queries_completed >= 2 AS completed_ok
+FROM pg_stat_resqueues
+WHERE queuename = 'rq_stats_test';
+
+-- All counter columns must be non-negative.
+0:SELECT
+ queries_submitted >= 0 AS sub_nn,
+ queries_admitted >= 0 AS adm_nn,
+ queries_rejected >= 0 AS rej_nn,
+ queries_completed >= 0 AS cmp_nn,
+ total_wait_time_secs >= 0 AS wait_nn,
+ max_wait_secs >= 0 AS maxw_nn,
+ total_exec_time_secs >= 0 AS exec_nn,
+ max_exec_secs >= 0 AS maxe_nn,
+ total_cost >= 0 AS cost_nn,
+ total_memory_kb >= 0 AS mem_nn
+FROM pg_stat_resqueues
+WHERE queuename = 'rq_stats_test';
+
+-- Verify pg_stat_get_resqueue_stats() returns data directly.
+-- The function has OUT parameters so no column definition list is needed.
+0:SELECT queries_submitted >= 0 AS ok
+FROM pg_stat_get_resqueue_stats(
+ (SELECT oid FROM pg_resqueue WHERE rsqname = 'rq_stats_test')
+ ) AS s;
+
+-- Cleanup.
+0:DROP ROLE role_stats_test;
+0:DROP RESOURCE QUEUE rq_stats_test;
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cloudberry.apache.org
For additional commands, e-mail: commits-help@cloudberry.apache.org