diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index e4b70166b0e50..b01a2e1e14928 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -3924,26 +3924,45 @@ show_indexsearches_info(PlanState *planstate, ExplainState *es) static void show_tidbitmap_info(BitmapHeapScanState *planstate, ExplainState *es) { + uint64 exact_pages; + uint64 lossy_pages; + if (!es->analyze) return; + /* Start with leader's stats */ + exact_pages = planstate->stats.exact_pages; + lossy_pages = planstate->stats.lossy_pages; + + /* Accumulate worker stats into node-level totals */ + if (planstate->sinstrument != NULL) + { + for (int n = 0; n < planstate->sinstrument->num_workers; n++) + { + BitmapHeapScanInstrumentation *si = &planstate->sinstrument->sinstrument[n]; + + exact_pages += si->exact_pages; + lossy_pages += si->lossy_pages; + } + } + if (es->format != EXPLAIN_FORMAT_TEXT) { ExplainPropertyUInteger("Exact Heap Blocks", NULL, - planstate->stats.exact_pages, es); + exact_pages, es); ExplainPropertyUInteger("Lossy Heap Blocks", NULL, - planstate->stats.lossy_pages, es); + lossy_pages, es); } else { - if (planstate->stats.exact_pages > 0 || planstate->stats.lossy_pages > 0) + if (exact_pages > 0 || lossy_pages > 0) { ExplainIndentText(es); appendStringInfoString(es->str, "Heap Blocks:"); - if (planstate->stats.exact_pages > 0) - appendStringInfo(es->str, " exact=" UINT64_FORMAT, planstate->stats.exact_pages); - if (planstate->stats.lossy_pages > 0) - appendStringInfo(es->str, " lossy=" UINT64_FORMAT, planstate->stats.lossy_pages); + if (exact_pages > 0) + appendStringInfo(es->str, " exact=" UINT64_FORMAT, exact_pages); + if (lossy_pages > 0) + appendStringInfo(es->str, " lossy=" UINT64_FORMAT, lossy_pages); appendStringInfoChar(es->str, '\n'); } } diff --git a/src/test/regress/expected/explain.out b/src/test/regress/expected/explain.out index 7c1f26b182cb0..b307e810ca561 100644 --- a/src/test/regress/expected/explain.out +++ b/src/test/regress/expected/explain.out @@ -822,3 +822,70 @@ select explain_filter('explain (analyze,buffers off,costs off) select sum(n) ove (9 rows) reset work_mem; +-- Test parallel bitmap heap scan reports per-worker heap block stats. +CREATE FUNCTION check_parallel_bitmap_heap_scan() RETURNS boolean AS $$ +DECLARE + plan_json json; + node json; +BEGIN + SET LOCAL enable_seqscan = off; + SET LOCAL enable_indexscan = off; + SET LOCAL parallel_setup_cost = 0; + SET LOCAL parallel_tuple_cost = 0; + SET LOCAL min_parallel_table_scan_size = 0; + SET LOCAL min_parallel_index_scan_size = 0; + SET LOCAL max_parallel_workers_per_gather = 2; + SET LOCAL parallel_leader_participation = off; + + EXECUTE 'EXPLAIN (ANALYZE, BUFFERS, COSTS OFF, FORMAT JSON) + SELECT count(*) FROM tenk1 WHERE hundred > 1' INTO plan_json; + + node := plan_json->0->'Plan'; + WHILE node->'Plans' IS NOT NULL AND node->>'Node Type' != 'Bitmap Heap Scan' LOOP + node := node->'Plans'->0; + END LOOP; + + RETURN COALESCE((node->>'Exact Heap Blocks')::int, 0) > 0; +END; +$$ LANGUAGE plpgsql; +SELECT check_parallel_bitmap_heap_scan() AS parallel_bitmap_instrumentation; + parallel_bitmap_instrumentation +--------------------------------- + t +(1 row) + +DROP FUNCTION check_parallel_bitmap_heap_scan; +-- Test parallel index-only scan reports per-worker index search stats. +CREATE FUNCTION check_parallel_indexonly_scan() RETURNS boolean AS $$ +DECLARE + plan_json json; + node json; +BEGIN + SET LOCAL enable_seqscan = off; + SET LOCAL enable_bitmapscan = off; + SET LOCAL parallel_setup_cost = 0; + SET LOCAL parallel_tuple_cost = 0; + SET LOCAL min_parallel_index_scan_size = 0; + SET LOCAL min_parallel_table_scan_size = 0; + SET LOCAL max_parallel_workers_per_gather = 2; + SET LOCAL parallel_leader_participation = off; + + EXECUTE 'EXPLAIN (ANALYZE, BUFFERS, COSTS OFF, FORMAT JSON) + SELECT count(*) FROM tenk1 WHERE thousand > 95' INTO plan_json; + + -- Drill down to the Index Only Scan node + node := plan_json->0->'Plan'; + WHILE node->'Plans' IS NOT NULL AND node->>'Node Type' != 'Index Only Scan' LOOP + node := node->'Plans'->0; + END LOOP; + + RETURN COALESCE((node->>'Index Searches')::int, 0) > 0; +END; +$$ LANGUAGE plpgsql; +SELECT check_parallel_indexonly_scan() AS parallel_indexonly_instrumentation; + parallel_indexonly_instrumentation +------------------------------------ + t +(1 row) + +DROP FUNCTION check_parallel_indexonly_scan; diff --git a/src/test/regress/sql/explain.sql b/src/test/regress/sql/explain.sql index ebdab42604beb..3a13fa6ca69a4 100644 --- a/src/test/regress/sql/explain.sql +++ b/src/test/regress/sql/explain.sql @@ -188,3 +188,66 @@ select explain_filter('explain (analyze,buffers off,costs off) select sum(n) ove -- Test tuplestore storage usage in Window aggregate (memory and disk case, final result is disk) select explain_filter('explain (analyze,buffers off,costs off) select sum(n) over(partition by m) from (SELECT n < 3 as m, n from generate_series(1,2500) a(n))'); reset work_mem; + +-- Test parallel bitmap heap scan reports per-worker heap block stats. +CREATE FUNCTION check_parallel_bitmap_heap_scan() RETURNS boolean AS $$ +DECLARE + plan_json json; + node json; +BEGIN + SET LOCAL enable_seqscan = off; + SET LOCAL enable_indexscan = off; + SET LOCAL parallel_setup_cost = 0; + SET LOCAL parallel_tuple_cost = 0; + SET LOCAL min_parallel_table_scan_size = 0; + SET LOCAL min_parallel_index_scan_size = 0; + SET LOCAL max_parallel_workers_per_gather = 2; + SET LOCAL parallel_leader_participation = off; + + EXECUTE 'EXPLAIN (ANALYZE, BUFFERS, COSTS OFF, FORMAT JSON) + SELECT count(*) FROM tenk1 WHERE hundred > 1' INTO plan_json; + + node := plan_json->0->'Plan'; + WHILE node->'Plans' IS NOT NULL AND node->>'Node Type' != 'Bitmap Heap Scan' LOOP + node := node->'Plans'->0; + END LOOP; + + RETURN COALESCE((node->>'Exact Heap Blocks')::int, 0) > 0; +END; +$$ LANGUAGE plpgsql; + +SELECT check_parallel_bitmap_heap_scan() AS parallel_bitmap_instrumentation; + +DROP FUNCTION check_parallel_bitmap_heap_scan; + +-- Test parallel index-only scan reports per-worker index search stats. +CREATE FUNCTION check_parallel_indexonly_scan() RETURNS boolean AS $$ +DECLARE + plan_json json; + node json; +BEGIN + SET LOCAL enable_seqscan = off; + SET LOCAL enable_bitmapscan = off; + SET LOCAL parallel_setup_cost = 0; + SET LOCAL parallel_tuple_cost = 0; + SET LOCAL min_parallel_index_scan_size = 0; + SET LOCAL min_parallel_table_scan_size = 0; + SET LOCAL max_parallel_workers_per_gather = 2; + SET LOCAL parallel_leader_participation = off; + + EXECUTE 'EXPLAIN (ANALYZE, BUFFERS, COSTS OFF, FORMAT JSON) + SELECT count(*) FROM tenk1 WHERE thousand > 95' INTO plan_json; + + -- Drill down to the Index Only Scan node + node := plan_json->0->'Plan'; + WHILE node->'Plans' IS NOT NULL AND node->>'Node Type' != 'Index Only Scan' LOOP + node := node->'Plans'->0; + END LOOP; + + RETURN COALESCE((node->>'Index Searches')::int, 0) > 0; +END; +$$ LANGUAGE plpgsql; + +SELECT check_parallel_indexonly_scan() AS parallel_indexonly_instrumentation; + +DROP FUNCTION check_parallel_indexonly_scan;