@@ -110,6 +110,7 @@ struct SharedExecutorInstrumentation
110110 * follows.
111111 */
112112};
113+
113114/*
114115 * While the query runs, each parallel worker writes its per-node statistics
115116 * directly into its own slot in this array (indexed by ParallelWorkerNumber),
@@ -252,6 +253,67 @@ ExecSerializePlan(Plan *plan, EState *estate)
252253 * While we're at it, count the number of PlanState nodes in the tree, so
253254 * we know how many Instrumentation structures we need.
254255 */
256+
257+ /*
258+ * Generic helpers for per-worker node instrumentation containers.
259+ *
260+ * Many node types collect a fixed-size instrumentation struct per parallel
261+ * worker in a SharedWorkerInstrumentation container (see instrument_node.h).
262+ * These helpers factor out the identical estimate/allocate/lookup/retrieve
263+ * plumbing; only the element size, the TOC key, and the gating conditions vary
264+ * between nodes, and those stay in the per-node callers.
265+ */
266+
267+ /* Reserve DSM space (one chunk + one key) for the container. */
268+ void
269+ ExecInstrEstimate (ParallelContext * pcxt , Size elemsz )
270+ {
271+ shm_toc_estimate_chunk (& pcxt -> estimator ,
272+ SharedWorkerInstrSize (pcxt -> nworkers , elemsz ));
273+ shm_toc_estimate_keys (& pcxt -> estimator , 1 );
274+ }
275+
276+ /* Allocate, zero, and publish the container under 'key'; returns it. */
277+ SharedWorkerInstrumentation *
278+ ExecInstrInitDSM (ParallelContext * pcxt , uint64 key , Size elemsz )
279+ {
280+ Size size = SharedWorkerInstrSize (pcxt -> nworkers , elemsz );
281+ SharedWorkerInstrumentation * si = shm_toc_allocate (pcxt -> toc , size );
282+
283+ /* ensure any unfilled slots will contain zeroes */
284+ memset (si , 0 , size );
285+ si -> num_workers = pcxt -> nworkers ;
286+ shm_toc_insert (pcxt -> toc , key , si );
287+ return si ;
288+ }
289+
290+ /* Worker side: look up the container previously published under 'key'. */
291+ SharedWorkerInstrumentation *
292+ ExecInstrInitWorker (shm_toc * toc , uint64 key , bool missing_ok )
293+ {
294+ return (SharedWorkerInstrumentation * ) shm_toc_lookup (toc , key , missing_ok );
295+ }
296+
297+ /*
298+ * Leader side: copy the container out of (soon to be freed) DSM into a
299+ * backend-local copy, so EXPLAIN can read it after the parallel context is
300+ * torn down. Returns NULL if there was no instrumentation.
301+ */
302+ SharedWorkerInstrumentation *
303+ ExecInstrRetrieve (SharedWorkerInstrumentation * shared , Size elemsz )
304+ {
305+ Size size ;
306+ SharedWorkerInstrumentation * si ;
307+
308+ if (shared == NULL )
309+ return NULL ;
310+
311+ size = SharedWorkerInstrSize (shared -> num_workers , elemsz );
312+ si = palloc (size );
313+ memcpy (si , shared , size );
314+ return si ;
315+ }
316+
255317static bool
256318ExecParallelEstimate (PlanState * planstate , ExecParallelEstimateContext * e )
257319{
@@ -873,10 +935,11 @@ ExecInitParallelPlan(PlanState *planstate, EState *estate,
873935 instrumentation -> num_plan_nodes = e .nnodes ;
874936
875937 /*
876- * Initialize each worker's slot. Workers write into these directly as
877- * the query runs and never reset them, so this must happen exactly once
878- * (not on relaunch, where the slots accumulate across rounds). The
879- * async_mode flag is fixed up by each worker once it knows its node.
938+ * Initialize each worker's slot. Workers write into these directly
939+ * as the query runs and never reset them, so this must happen exactly
940+ * once (not on relaunch, where the slots accumulate across rounds).
941+ * The async_mode flag is fixed up by each worker once it knows its
942+ * node.
880943 */
881944 for (node = 0 ; node < e .nnodes ; ++ node )
882945 for (worker = 0 ; worker < nworkers ; ++ worker )
@@ -1127,9 +1190,9 @@ ExecParallelRetrieveInstrumentation(PlanState *planstate,
11271190 elog (ERROR , "plan node %d not found" , plan_node_id );
11281191
11291192 /*
1130- * Accumulate the statistics from all workers. The workers already flushed
1131- * their final cycle with InstrEndLoop before exiting, so no InstrEndLoop is
1132- * needed here.
1193+ * Accumulate the statistics from all workers. The workers already
1194+ * flushed their final cycle with InstrEndLoop before exiting, so no
1195+ * InstrEndLoop is needed here.
11331196 */
11341197 for (n = 0 ; n < instrumentation -> num_workers ; ++ n )
11351198 InstrAggNode (planstate -> instrument ,
@@ -1412,9 +1475,9 @@ ExecParallelRepointInstrumentation(PlanState *planstate,
14121475
14131476 /*
14141477 * The leader initialized the slot's options but cannot know each node's
1415- * async mode, so carry that over from the local instrumentation. We do not
1416- * otherwise reset the slot: when workers are relaunched (e.g. a rescanned
1417- * Gather) the slots accumulate across rounds.
1478+ * async mode, so carry that over from the local instrumentation. We do
1479+ * not otherwise reset the slot: when workers are relaunched (e.g. a
1480+ * rescanned Gather) the slots accumulate across rounds.
14181481 */
14191482 slot -> async_mode = planstate -> instrument -> async_mode ;
14201483
@@ -1455,10 +1518,11 @@ ExecParallelInitializeWorker(PlanState *planstate, ParallelWorkerContext *pwcxt)
14551518 switch (nodeTag (planstate ))
14561519 {
14571520 case T_SeqScanState :
1521+
14581522 /*
14591523 * Set up instrumentation first (even when not parallel-aware, for
1460- * EXPLAIN ANALYZE) so that a parallel-aware scan can be pointed at
1461- * the worker's shared I/O stats slot when it begins.
1524+ * EXPLAIN ANALYZE) so that a parallel-aware scan can be pointed
1525+ * at the worker's shared I/O stats slot when it begins.
14621526 */
14631527 ExecSeqScanInstrumentInitWorker ((SeqScanState * ) planstate , pwcxt );
14641528 if (planstate -> plan -> parallel_aware )
@@ -1491,10 +1555,11 @@ ExecParallelInitializeWorker(PlanState *planstate, ParallelWorkerContext *pwcxt)
14911555 pwcxt );
14921556 break ;
14931557 case T_TidRangeScanState :
1558+
14941559 /*
14951560 * Set up instrumentation first (even when not parallel-aware, for
1496- * EXPLAIN ANALYZE) so that a parallel-aware scan can be pointed at
1497- * the worker's shared I/O stats slot when it begins.
1561+ * EXPLAIN ANALYZE) so that a parallel-aware scan can be pointed
1562+ * at the worker's shared I/O stats slot when it begins.
14981563 */
14991564 ExecTidRangeScanInstrumentInitWorker ((TidRangeScanState * ) planstate ,
15001565 pwcxt );
@@ -1624,9 +1689,9 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
16241689 ExecParallelInitializeWorker (queryDesc -> planstate , & pwcxt );
16251690
16261691 /*
1627- * If instrumentation is enabled, point each node's instrumentation at this
1628- * worker's slot in shared memory so the executor writes statistics there
1629- * directly, with no copy needed at shutdown.
1692+ * If instrumentation is enabled, point each node's instrumentation at
1693+ * this worker's slot in shared memory so the executor writes statistics
1694+ * there directly, with no copy needed at shutdown.
16301695 */
16311696 if (instrumentation != NULL )
16321697 ExecParallelRepointInstrumentation (queryDesc -> planstate , instrumentation );
@@ -1662,8 +1727,8 @@ ParallelQueryMain(dsm_segment *seg, shm_toc *toc)
16621727
16631728 /*
16641729 * Finalize instrumentation if any instrumentation options are set. The
1665- * statistics were written straight into shared memory during execution, so
1666- * this just flushes each node's final run cycle.
1730+ * statistics were written straight into shared memory during execution,
1731+ * so this just flushes each node's final run cycle.
16671732 */
16681733 if (instrumentation != NULL )
16691734 ExecParallelFinishInstrumentation (queryDesc -> planstate , NULL );
0 commit comments