Feature: add pg_query_state runtime API to gp_stats_collector - #52
Feature: add pg_query_state runtime API to gp_stats_collector#52roaldm153 wants to merge 17 commits into
Conversation
Fold three PostgreSQL core changes the gp_stats_collector extension depends on directly into the sources: custom ProcSignal handlers (procsignal.c/.h, postgres.c), end-of-node instrumentation (instrument.c/.h), and runtime EXPLAIN entry points (explain.c/.h). configure enables the extension by default, so the tree must build without a manual patch step.
Signal-dispatch module and executor-lifecycle hooks that walk the live plan tree of a running backend on demand, plus the SQL API (extension v1.2): pg_query_state(pid), pg_query_state_backends(pid), cbdb_mpp_query_state(). Functions are granted to PUBLIC and guarded in C: a caller may poll a backend only if it is a superuser or owns the target query (GetUserId() == proc->roleId), so monitoring agents can run as a non-superuser while access stays restricted.
Builds Cloudberry with the extension across ubuntu22.04/rocky8/rocky9, stands up a demo cluster with gp_stats_collector preloaded, and runs the pg_regress and multi-session isolation2 suites.
There was a problem hiding this comment.
Hi, @roaldm153 welcome!🎊 Thanks for taking the effort to make our project better! 🙌 Keep making such awesome contributions!
pg_regress contract: catalog registration and input-validation errors. isolation2 (pg_query_state multi-session): idle backend, happy-path poll of a suspended query, the permission gate, STAT_DISABLED, and a strict one-backend-per-primary-segment count.
The signal-dispatch layer under src/pg_query_state/ derives from pg_query_state (https://github.com/postgrespro/pg_query_state, PostgreSQL License). Add dual license headers (ASF + Portions Copyright Postgres Professional) to the derived files (pg_query_state.c/.h, signal_handler.c), an ASF header to qs_types.h (original Cloudberry code, the stray Postgres Professional line removed), and a PostgreSQL-License notice for pg_query_state in the root LICENSE.
9529650 to
290faa5
Compare
pg_qs_executor_start() enabled instrumentation and called cdbexplain_showExecStatsBegin() on both QD and QE backends. That function aggregates per-QE statistics on the dispatcher and opens with Assert(Gp_role != GP_ROLE_EXECUTE), so on a QE it trips the assertion in a cassert build (a harmless no-op in a release build, which is why it went unnoticed). The DTX recovery worker runs the executor on segments with Gp_role == GP_ROLE_EXECUTE, so distributed-transaction recovery crashed the segment and left the cluster looping in reset/recovery mode. Guard the call with GP_ROLE_DISPATCH. QE backends still receive instrument_options, which is all the per-node plan-tree walker reads; the showstatctx aggregation is a coordinator-only concern.
Threads a 16-byte trace_id through the collection so it matches the catalog contract of the UDS per-node branch while this variant keeps writing per-node snapshots to the server log: - pg_query_state(pid, trace_id bytea) and cbdb_mpp_query_state(gp_segment_pid[], trace_id bytea); trace_id is validated to 16 bytes and stamped into a new per-backend shmem slot (toc key 3) before each backend is signalled, so the handler knows which collection its snapshot belongs to. - SendCdbComponents sizes the backend-info reply by the number of descriptors it actually emits (sum of each segment's activelist) rather than numActiveQEs; the two diverge for coordinator-heavy plans (INSERT ... SELECT) and overran the allocation, which the receiver rejected as "unexpected message length". - SQL migrations, catalog-contract and isolation2 specs updated for the new signatures.
Remove one-line comments that paraphrase the declaration or the next statement (the saved-hook/module-initialized globals, the outer/inner walker recursion). Keep the ones that carry non-obvious rationale.
pg_qs_executor_end walked the whole plan tree and logged per-node stats for every query on every backend, even when pg_query_state was disabled or the query was never instrumented. Gate the walk on the instrumented marker (queryDesc->totaltime, set in pg_qs_executor_start under the same predicate) so uninstrumented queries pay nothing. cbdb_mpp_query_state only checked for a NULL proc before indexing qs_trace_slots[proc->backendId] and signalling; an InvalidBackendId (-1) would underflow the slot array and misroute the signal. Skip it, matching the QD entry point.
6f8a4d5 to
98bad86
Compare
There was a problem hiding this comment.
Pull request overview
Adds a signal-driven “runtime query state” facility to Cloudberry’s gp_stats_collector extension by embedding a pg_query_state-derived signal layer and introducing small additive hooks in core PostgreSQL/GPDB areas (ProcSignal, instrumentation, and EXPLAIN) to allow mid-execution sampling across QD/QEs.
Changes:
- Introduces custom ProcSignal reasons/handlers and plumbing to register/dispatch extension-defined signals.
- Extends executor instrumentation and EXPLAIN to support mid-run (“runtime”) observation without finalizing node stats.
- Adds gp_stats_collector extension v1.2 SQL API plus pg_regress + isolation2 test suites and a dedicated CI workflow.
Reviewed changes
Copilot reviewed 40 out of 40 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/include/storage/procsignal.h | Adds custom ProcSignal slots/reasons and APIs for registering/handling custom signals. |
| src/include/executor/instrument.h | Adds Instrumentation.eof to support safe mid-run “node exhausted” detection. |
| src/include/commands/explain.h | Adds ExplainState.runtime flag for runtime explain paths. |
| src/backend/tcop/postgres.c | Wires custom signal handling into interrupt processing; adds -Z flag passthrough. |
| src/backend/storage/ipc/procsignal.c | Implements custom ProcSignal registration and custom-signal handling loop. |
| src/backend/executor/instrument.c | Tracks and resets new Instrumentation.eof field. |
| src/backend/commands/explain.c | Adjusts EXPLAIN/ANALYZE reporting for runtime mode and mid-run snapshots. |
| pom.xml | Updates licensing-scan exclusions for new test harness files. |
| LICENSE | Adds PostgreSQL-License notice for pg_query_state-derived code. |
| gpcontrib/gp_stats_collector/test/sql/gpsc_pg_query_state.sql | Adds pg_regress coverage for catalog contract and negative paths. |
| gpcontrib/gp_stats_collector/test/Makefile | Adds installcheck harness for the new pg_regress suite. |
| gpcontrib/gp_stats_collector/test/isolation2/sql/setup.sql | Adds shared isolation2 setup (extensions). |
| gpcontrib/gp_stats_collector/test/isolation2/sql/gpsc_pqs_seg_count.sql | Adds isolation2 test for “one QE backend per primary segment”. |
| gpcontrib/gp_stats_collector/test/isolation2/sql/gpsc_pqs_running.sql | Adds isolation2 happy-path test for polling a suspended running query. |
| gpcontrib/gp_stats_collector/test/isolation2/sql/gpsc_pqs_perms.sql | Adds isolation2 permissions gate coverage. |
| gpcontrib/gp_stats_collector/test/isolation2/sql/gpsc_pqs_disabled.sql | Adds isolation2 coverage for per-session module disable behavior. |
| gpcontrib/gp_stats_collector/test/isolation2/sql/gpsc_pqs_backends.sql | Adds isolation2 coverage for polling an idle backend. |
| gpcontrib/gp_stats_collector/test/isolation2/Makefile | Adds installcheck harness for the isolation2 suite. |
| gpcontrib/gp_stats_collector/test/isolation2/isolation2_schedule | Adds isolation2 schedule for the pg_query_state suite. |
| gpcontrib/gp_stats_collector/test/isolation2/expected/setup.out | Expected output for setup. |
| gpcontrib/gp_stats_collector/test/isolation2/expected/gpsc_pqs_seg_count.out | Expected output for seg-count test. |
| gpcontrib/gp_stats_collector/test/isolation2/expected/gpsc_pqs_running.out | Expected output for running-query test. |
| gpcontrib/gp_stats_collector/test/isolation2/expected/gpsc_pqs_perms.out | Expected output for permissions test. |
| gpcontrib/gp_stats_collector/test/isolation2/expected/gpsc_pqs_disabled.out | Expected output for disabled-mode test. |
| gpcontrib/gp_stats_collector/test/isolation2/expected/gpsc_pqs_backends.out | Expected output for idle-backend test. |
| gpcontrib/gp_stats_collector/test/isolation2/.gitignore | Ignores generated isolation2 artifacts. |
| gpcontrib/gp_stats_collector/test/expected/gpsc_pg_query_state.out | Expected output for pg_regress contract/negative tests. |
| gpcontrib/gp_stats_collector/src/pg_query_state/signal_handler.c | Implements signal handlers and plan-tree walking/logging for snapshots. |
| gpcontrib/gp_stats_collector/src/pg_query_state/qs_types.h | Defines per-node sample struct and enums for runtime snapshots. |
| gpcontrib/gp_stats_collector/src/pg_query_state/pg_query_state.h | Defines shared-memory + protocol structs and public APIs for pg_query_state layer. |
| gpcontrib/gp_stats_collector/src/pg_query_state/pg_query_state.c | Implements shmem setup, GUCs, executor hooks, and SQL entry points. |
| gpcontrib/gp_stats_collector/src/hook_wrappers.cpp | Integrates pg_query_state executor lifecycle hooks into existing extension hooks. |
| gpcontrib/gp_stats_collector/src/GpscStat.cpp | Updates shmem hook handling (incl. PG15 shmem_request_hook) and documentation/comments. |
| gpcontrib/gp_stats_collector/src/gp_stats_collector.c | Calls pg_qs_init() from _PG_init() to register shmem/custom signals at startup. |
| gpcontrib/gp_stats_collector/README.md | Documents the new runtime query state API and configuration. |
| gpcontrib/gp_stats_collector/Makefile | Adds include paths needed by new pg_query_state code. |
| gpcontrib/gp_stats_collector/gp_stats_collector.control | Bumps extension default version to 1.2. |
| gpcontrib/gp_stats_collector/gp_stats_collector--1.2.sql | Adds v1.2 objects: types, functions, grants for pg_query_state API. |
| gpcontrib/gp_stats_collector/gp_stats_collector--1.1--1.2.sql | Adds upgrade script introducing pg_query_state API. |
| .github/workflows/gpsc-ci.yaml | Adds dedicated CI workflow covering build + regress + isolation2 for the extension. |
Suppressed comments (1)
gpcontrib/gp_stats_collector/src/pg_query_state/pg_query_state.h:111
- The comment references TIMINIG_OFF_WARNING (misspelled). If an alias is kept for compatibility, the primary documented name should be TIMING_OFF_WARNING.
int warnings; /* bitmask of TIMINIG_OFF_WARNING / BUFFERS_OFF_WARNING */
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Bring the signal-only base up to the full trace_id/UDS pipeline: - Key every collection by a 16-byte trace_id minted per pg_query_state() invocation; the SQL API gains a bytea trace_id argument and the dispatcher stamps each target's per-backend trace slot before signalling. - Push per-node batches and the coordinator plan-doc to the local yagpcc over UDS as protobuf (SetPerNodeBatchReq / SetQueryPlanReq) instead of the server log; add the C++ PlanNodeEmitter bridge and the yagpcc protos. - Emit derived per-node rate fields (ntuples_delta, tuples_per_sec, time_since_init_sec, stalled) computed on the C side from a per-node rolling state, reset on executor start/end. - Harden the UDS connect/send path against backpressure (bounded poll), report the coordinator itself for QD-only queries, and apply the review-driven lock/leak/NULL-safety fixes to the entry points.
run.sh -c local ships the whole repo as the Docker build context. Without this, object files, shared libs and generated protobuf sources from a host or devcontainer build get copied into the image; the in-image make reuses those stale .o (compiled against the dev environment's Xerces/GSS headers) and the link fails with undefined xercesc_3_2::* / pqsecure_open_gss. Excluding them forces a clean in-image build.
What does this PR do?
Adds a signal-based runtime query-state facility to the
gp_stats_collectorextension. It lets a session inspect the live execution state of another running backend on demand - walking its active plan tree across the QD and all QEs - without waiting for the query to finish.New SQL API (extension v1.2, schema
gpsc):pg_query_state(pid)- fan out a poll to the query running onpid; each participating backend walks its plan tree and logs a per-node snapshot.pg_query_state_backends(pid)- list the(segid, pid)QE backends taking part in that query.cbdb_mpp_query_state(gp_segment_pid[])- QE-side dispatch target.The extension embeds the
pg_query_statesignal layer, which depends on three PostgreSQL core changes folded directly into the tree (configure enables the extension by default, so the tree must build without a manual patch step):ProcSignalhandlers (procsignal.c/.h,postgres.c);instrument.c/.h);EXPLAINentry points (explain.c/.h).Type of Change
Breaking Changes
None. Core changes are additive (new signal reasons, a new Instrumentation field, new runtime-EXPLAIN paths); existing behavior is unchanged.
Test Plan
Unit tests added/updated
Integration tests added/updated
Passed
make installcheckpg_regress (
gpcontrib/gp_stats_collector/test): catalog contract (function/type registration, exec location) and input-validation errors.isolation2 (
gpcontrib/gp_stats_collector/test/isolation2), multi-session:gpsc_pqs_backends- idle backend yields an empty list;gpsc_pqs_running- happy path: poll a query suspended on the QEs via a fault injector;gpsc_pqs_perms- permission gate (non-owner non-superuser denied);gpsc_pqs_disabled-STAT_DISABLEDreports no backends;gpsc_pqs_seg_count- one backend per primary segment.A dedicated CI workflow builds Cloudberry with the extension across ubuntu22.04/rocky8/rocky9 and runs the pg_regress, isolation2, and core regression suites.
Impact
Performance:
No steady-state cost. Work happens only when a poll is issued: one signal fan-out plus a bounded shared-memory round-trip; the shmem lock is held only around the request, not during dispatch.
User-facing changes:
New
gpsc.pg_query_state*functions and thepg_query_state.enableGUC. Functions are granted to PUBLIC; access is gated in C so a caller may poll a backend only if it is a superuser or owns the target query.Dependencies:
None.
Checklist
Additional Context
The signal layer under
src/pg_query_state/is derived from pg_query_state (PostgreSQL License). Derived files carry dual license headers (ASF +Portions Copyright Postgres Professional) and the rootLICENSErecords a PostgreSQL-License notice for it. Happy to provide provenance details for IP clearance.