From fe65713ecad2fb46cf66adb3e2e58307ee0432d8 Mon Sep 17 00:00:00 2001
From: James Coleman <jtc331@gmail.com>
Date: Sat, 7 Mar 2020 17:09:39 -0500
Subject: [PATCH 3/5] Rework EXPLAIN for incremental sort

---
 src/backend/commands/explain.c                | 253 +++++++++---------
 src/backend/executor/nodeIncrementalSort.c    | 121 ++++++---
 src/include/nodes/execnodes.h                 |  29 +-
 .../regress/expected/incremental_sort.out     | 160 +++++++++++
 src/test/regress/sql/incremental_sort.sql     |  10 +
 5 files changed, 402 insertions(+), 171 deletions(-)

diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 14aedec919..8262c54e6a 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -2701,80 +2701,114 @@ show_sort_info(SortState *sortstate, ExplainState *es)
 	}
 }
 
-/*
- * If it's EXPLAIN ANALYZE, show tuplesort stats for a incremental sort node
- */
+
 static void
-show_incremental_sort_info(IncrementalSortState *incrsortstate,
-						   ExplainState *es)
+show_incremental_sort_group_info(IncrementalSortGroupInfo *groupInfo,
+						   const char *groupLabel, ExplainState *es)
 {
-	if (es->analyze && incrsortstate->sort_Done &&
-		incrsortstate->fullsort_state != NULL)
+	const char *sortMethodName;
+	const char *spaceTypeName;
+	ListCell *methodCell;
+	int methodCount = list_length(groupInfo->sortMethods);
+
+	if (es->format == EXPLAIN_FORMAT_TEXT)
 	{
-		/* TODO: is it valid to get space used etc. only once given we re-use the sort? */
-		/* TODO: maybe show average, min, max sort group size? */
-
-		Tuplesortstate *fullsort_state = incrsortstate->fullsort_state;
-		TuplesortInstrumentation fullsort_stats;
-		const char *fullsort_sortMethod;
-		const char *fullsort_spaceType;
-		Tuplesortstate *prefixsort_state = incrsortstate->prefixsort_state;
-		TuplesortInstrumentation prefixsort_stats;
-		const char *prefixsort_sortMethod;
-		const char *prefixsort_spaceType;
-
-		tuplesort_get_stats(fullsort_state, &fullsort_stats);
-		fullsort_sortMethod = tuplesort_method_name(fullsort_stats.sortMethod);
-		fullsort_spaceType = tuplesort_space_type_name(fullsort_stats.spaceType);
-		if (prefixsort_state != NULL)
+		appendStringInfoSpaces(es->str, es->indent * 2);
+		appendStringInfo(es->str, "%s Groups: %ld (Methods: ", groupLabel,
+						 groupInfo->groupCount);
+		foreach(methodCell, groupInfo->sortMethods)
 		{
-			tuplesort_get_stats(prefixsort_state, &prefixsort_stats);
-			prefixsort_sortMethod = tuplesort_method_name(prefixsort_stats.sortMethod);
-			prefixsort_spaceType = tuplesort_space_type_name(prefixsort_stats.spaceType);
+			sortMethodName = tuplesort_method_name(methodCell->int_value);
+			appendStringInfo(es->str, "%s", sortMethodName);
+			if (foreach_current_index(methodCell) < methodCount - 1)
+				appendStringInfo(es->str, ", ");
 		}
+		appendStringInfo(es->str, ")");
 
-		if (es->format == EXPLAIN_FORMAT_TEXT)
+		if (groupInfo->maxMemorySpaceUsed > 0)
 		{
-			appendStringInfoSpaces(es->str, es->indent * 2);
-			appendStringInfo(es->str, "Sort Method: Full: %s  %s: %ldkB",
-							 fullsort_sortMethod, fullsort_spaceType,
-							 fullsort_stats.spaceUsed);
-			if (prefixsort_state != NULL)
-				appendStringInfo(es->str, ", Prefix-only: %s %s: %ldkB\n",
-								 prefixsort_sortMethod, prefixsort_spaceType,
-								 prefixsort_stats.spaceUsed);
-			else
-				appendStringInfo(es->str, "\n");
-			appendStringInfoSpaces(es->str, es->indent * 2);
-			appendStringInfo(es->str, "Sort Groups: Full:  %ld",
-							 incrsortstate->fullsort_group_count);
-			if (prefixsort_state != NULL)
-				appendStringInfo(es->str, ", Prefix-only: %ld\n",
-							 incrsortstate->prefixsort_group_count);
-			else
-				appendStringInfo(es->str, "\n");
+			long avgSpace = groupInfo->totalMemorySpaceUsed / groupInfo->groupCount;
+			spaceTypeName = tuplesort_space_type_name(SORT_SPACE_TYPE_MEMORY);
+			appendStringInfo(es->str, " %s: %ldkB (avg), %ldkB (max)",
+							 spaceTypeName, avgSpace,
+							 groupInfo->maxMemorySpaceUsed);
 		}
-		else
+
+		if (groupInfo->maxDiskSpaceUsed > 0)
 		{
-			/* TODO */
-			ExplainPropertyText("Full Sort Method", fullsort_sortMethod, es);
-			ExplainPropertyInteger("Full Sort Space Used", "kB",
-					fullsort_stats.spaceUsed, es);
-			ExplainPropertyText("Full Sort Space Type", fullsort_spaceType, es);
-			ExplainPropertyInteger("Full Sort Groups", NULL,
-								   incrsortstate->fullsort_group_count, es);
-
-			if (prefixsort_state != NULL)
-			{
-				ExplainPropertyText("Prefix Sort Method", prefixsort_sortMethod, es);
-				ExplainPropertyInteger("Prefix Sort Space Used", "kB",
-						prefixsort_stats.spaceUsed, es);
-				ExplainPropertyText("Prefix Sort Space Type", prefixsort_spaceType, es);
-				ExplainPropertyInteger("Prefix Sort Groups", NULL,
-									   incrsortstate->prefixsort_group_count, es);
-			}
+			long avgSpace = groupInfo->totalDiskSpaceUsed / groupInfo->groupCount;
+			spaceTypeName = tuplesort_space_type_name(SORT_SPACE_TYPE_DISK);
+			/* Add a semicolon separator only if memory stats were printed. */
+			if (groupInfo->maxMemorySpaceUsed > 0)
+				appendStringInfo(es->str, ";");
+			appendStringInfo(es->str, " %s: %ldkB (avg), %ldkB (max)",
+							 spaceTypeName, avgSpace,
+							 groupInfo->maxDiskSpaceUsed);
+		}
+
+		appendStringInfo(es->str, "\n");
+	}
+	else
+	{
+		List *methodNames = NIL;
+		StringInfoData groupName;
+
+		initStringInfo(&groupName);
+		appendStringInfo(&groupName, "%s Groups", groupLabel);
+		ExplainOpenGroup("Incremental Sort Groups", groupName.data, true, es);
+		ExplainPropertyInteger("Group Count", NULL, groupInfo->groupCount, es);
+
+		foreach(methodCell, groupInfo->sortMethods)
+		{
+			sortMethodName = tuplesort_method_name(methodCell->int_value);
+			methodNames = lappend(methodNames, sortMethodName);
+		}
+		ExplainPropertyList("Sort Methods Used", methodNames, es);
+
+		if (groupInfo->maxMemorySpaceUsed > 0)
+		{
+			long avgSpace = groupInfo->totalMemorySpaceUsed / groupInfo->groupCount;
+
+			ExplainPropertyInteger("Average Sort Space Used", "kB", avgSpace, es);
+			ExplainPropertyInteger("Maximum Sort Space Used", "kB",
+					groupInfo->maxMemorySpaceUsed, es);
+			spaceTypeName = tuplesort_space_type_name(SORT_SPACE_TYPE_MEMORY);
+			ExplainPropertyText("Sort Space Type", spaceTypeName, es);
+		}
+		if (groupInfo->maxDiskSpaceUsed > 0)
+		{
+			long avgSpace = groupInfo->totalDiskSpaceUsed / groupInfo->groupCount;
+
+			ExplainPropertyInteger("Average Sort Space Used", "kB", avgSpace, es);
+			ExplainPropertyInteger("Maximum Sort Space Used", "kB",
+					groupInfo->maxDiskSpaceUsed, es);
+			spaceTypeName = tuplesort_space_type_name(SORT_SPACE_TYPE_MEMORY);
+			ExplainPropertyText("Sort Space Type", spaceTypeName, es);
 		}
+
+		ExplainCloseGroup("Incremental Sort Groups", "XXX Groups", true, es);
 	}
+}
+
+/*
+ * If it's EXPLAIN ANALYZE, show tuplesort stats for a incremental sort node
+ */
+static void
+show_incremental_sort_info(IncrementalSortState *incrsortstate,
+						   ExplainState *es)
+{
+	IncrementalSortGroupInfo *fullsortGroupInfo;
+	IncrementalSortGroupInfo *prefixsortGroupInfo;
+
+	if (!(es->analyze && incrsortstate->sort_Done))
+		return;
+
+	fullsortGroupInfo = &incrsortstate->incsort_info.fullsortGroupInfo;
+	if (fullsortGroupInfo->groupCount > 0)
+		show_incremental_sort_group_info(fullsortGroupInfo, "Full-sort", es);
+	prefixsortGroupInfo = &incrsortstate->incsort_info.prefixsortGroupInfo;
+	if (prefixsortGroupInfo->groupCount > 0)
+		show_incremental_sort_group_info(prefixsortGroupInfo, "Presorted", es);
 
 	if (incrsortstate->shared_info != NULL)
 	{
@@ -2785,79 +2819,36 @@ show_incremental_sort_info(IncrementalSortState *incrsortstate,
 		{
 			IncrementalSortInfo *incsort_info =
 				&incrsortstate->shared_info->sinfo[n];
-			TuplesortInstrumentation *fullsort_instrument;
-			const char *fullsort_sortMethod;
-			const char *fullsort_spaceType;
-			long		fullsort_spaceUsed;
-			int64		fullsort_group_count;
-			TuplesortInstrumentation *prefixsort_instrument;
-			const char *prefixsort_sortMethod;
-			const char *prefixsort_spaceType;
-			long		prefixsort_spaceUsed;
-			int64		prefixsort_group_count;
-
-			fullsort_instrument = &incsort_info->fullsort_instrument;
-			fullsort_group_count = incsort_info->fullsort_group_count;
-
-			prefixsort_instrument = &incsort_info->prefixsort_instrument;
-			prefixsort_group_count = incsort_info->prefixsort_group_count;
-
-			if (fullsort_instrument->sortMethod == SORT_TYPE_STILL_IN_PROGRESS)
-				continue;		/* ignore any unfilled slots */
-
-			fullsort_sortMethod = tuplesort_method_name(
-					fullsort_instrument->sortMethod);
-			fullsort_spaceType = tuplesort_space_type_name(
-					fullsort_instrument->spaceType);
-			fullsort_spaceUsed = fullsort_instrument->spaceUsed;
+			/*
+			 * XXX: The previous version of the patch chcked:
+			 * fullsort_instrument->sortMethod == SORT_TYPE_STILL_IN_PROGRESS
+			 * and continued if the condition was true (with the comment "ignore
+			 * any unfilled slots").
+			 * I'm not convinced that makes sense since the same sort instrument
+			 * can have been used multiple times, so the last time it being used
+			 * being still in progress, doesn't seem to be relevant.
+			 * Instead I'm now checking to see if the group count for each group
+			 * info is 0. If both are 0, then we exclude the worker since it
+			 * didn't contribute anything meaningful.
+			 */
+			fullsortGroupInfo = &incsort_info->fullsortGroupInfo;
+			prefixsortGroupInfo = &incsort_info->prefixsortGroupInfo;
+			if (fullsortGroupInfo->groupCount == 0 &&
+					prefixsortGroupInfo->groupCount == 0)
+				continue;
 
-			if (prefixsort_instrument)
+			if (!opened_group)
 			{
-				prefixsort_sortMethod = tuplesort_method_name(
-						prefixsort_instrument->sortMethod);
-				prefixsort_spaceType = tuplesort_space_type_name(
-						prefixsort_instrument->spaceType);
-				prefixsort_spaceUsed = prefixsort_instrument->spaceUsed;
+				ExplainOpenGroup("Workers", "Workers", false, es);
+				opened_group = true;
 			}
 
-			if (es->format == EXPLAIN_FORMAT_TEXT)
-			{
-				appendStringInfoSpaces(es->str, es->indent * 2);
-				appendStringInfo(es->str,
-								 "Worker %d: Full Sort Method: %s  %s: %ldkB  Groups: %ld",
-								 n, fullsort_sortMethod, fullsort_spaceType,
-								 fullsort_spaceUsed, fullsort_group_count);
-				if (prefixsort_instrument)
-					appendStringInfo(es->str,
-									 ", Prefix Sort Method: %s  %s: %ldkB  Groups: %ld\n",
-									 prefixsort_sortMethod, prefixsort_spaceType,
-									 prefixsort_spaceUsed, prefixsort_group_count);
-				else
-					appendStringInfo(es->str, "\n");
-			}
-			else
-			{
-				if (!opened_group)
-				{
-					ExplainOpenGroup("Workers", "Workers", false, es);
-					opened_group = true;
-				}
-				ExplainOpenGroup("Worker", NULL, true, es);
-				ExplainPropertyInteger("Worker Number", NULL, n, es);
-				ExplainPropertyText("Full Sort Method", fullsort_sortMethod, es);
-				ExplainPropertyInteger("Full Sort Space Used", "kB", fullsort_spaceUsed, es);
-				ExplainPropertyText("Full Sort Space Type", fullsort_spaceType, es);
-				ExplainPropertyInteger("Full Sort Groups", NULL, fullsort_group_count, es);
-				if (prefixsort_instrument)
-				{
-					ExplainPropertyText("Prefix Sort Method", prefixsort_sortMethod, es);
-					ExplainPropertyInteger("Prefix Sort Space Used", "kB", prefixsort_spaceUsed, es);
-					ExplainPropertyText("Prefix Sort Space Type", prefixsort_spaceType, es);
-					ExplainPropertyInteger("Prefix Sort Groups", NULL, prefixsort_group_count, es);
-				}
-				ExplainCloseGroup("Worker", NULL, true, es);
-			}
+			if (fullsortGroupInfo->groupCount > 0)
+				show_incremental_sort_group_info(fullsortGroupInfo, "Full-sort", es);
+			if (prefixsortGroupInfo->groupCount > 0)
+				show_incremental_sort_group_info(prefixsortGroupInfo, "Presorted", es);
 		}
+
 		if (opened_group)
 			ExplainCloseGroup("Workers", "Workers", false, es);
 	}
diff --git a/src/backend/executor/nodeIncrementalSort.c b/src/backend/executor/nodeIncrementalSort.c
index c3b903e568..e6f749a798 100644
--- a/src/backend/executor/nodeIncrementalSort.c
+++ b/src/backend/executor/nodeIncrementalSort.c
@@ -68,6 +68,47 @@
 #include "utils/lsyscache.h"
 #include "utils/tuplesort.h"
 
+static void
+instrumentSortedGroup(PlanState *pstate, IncrementalSortGroupInfo *groupInfo,
+	Tuplesortstate *sortState)
+{
+	IncrementalSortState *node = castNode(IncrementalSortState, pstate);
+	TuplesortInstrumentation	sort_instr;
+
+	groupInfo->groupCount++;
+
+	tuplesort_get_stats(sortState, &sort_instr);
+	switch (sort_instr.spaceType)
+	{
+		case SORT_SPACE_TYPE_DISK:
+			groupInfo->totalDiskSpaceUsed += sort_instr.spaceUsed;
+			if (sort_instr.spaceUsed > groupInfo->maxDiskSpaceUsed)
+				groupInfo->maxDiskSpaceUsed = sort_instr.spaceUsed;
+
+			break;
+		case SORT_SPACE_TYPE_MEMORY:
+			groupInfo->totalMemorySpaceUsed += sort_instr.spaceUsed;
+			if (sort_instr.spaceUsed > groupInfo->maxMemorySpaceUsed)
+				groupInfo->maxMemorySpaceUsed = sort_instr.spaceUsed;
+
+			break;
+	}
+
+	if (!list_member_int(groupInfo->sortMethods, sort_instr.sortMethod))
+		groupInfo->sortMethods = lappend_int(groupInfo->sortMethods,
+				sort_instr.sortMethod);
+
+	/* Record shared stats if we're a parallel worker. */
+	if (node->shared_info && node->am_worker)
+	{
+		Assert(IsParallelWorker());
+		Assert(ParallelWorkerNumber <= node->shared_info->num_workers);
+
+		memcpy(&node->shared_info->sinfo[ParallelWorkerNumber],
+				&node->incsort_info, sizeof(IncrementalSortInfo));
+	}
+}
+
 /*
  * Prepare information for presorted_keys comparison.
  */
@@ -199,8 +240,9 @@ isCurrentGroup(IncrementalSortState *node, TupleTableSlot *pivot, TupleTableSlot
  * one different prefix key group before the large prefix key group.
  */
 static void
-switchToPresortedPrefixMode(IncrementalSortState *node)
+switchToPresortedPrefixMode(PlanState *pstate)
 {
+	IncrementalSortState *node = castNode(IncrementalSortState, pstate);
 	ScanDirection		dir;
 	int64 nTuples = 0;
 	bool lastTuple = false;
@@ -355,7 +397,11 @@ switchToPresortedPrefixMode(IncrementalSortState *node)
 		 */
 		SO1_printf("Sorting presorted prefix tuplesort with %ld tuples\n", nTuples);
 		tuplesort_performsort(node->prefixsort_state);
-		node->prefixsort_group_count++;
+
+		if (pstate->instrument != NULL)
+			instrumentSortedGroup(pstate,
+					&node->incsort_info.prefixsortGroupInfo,
+					node->prefixsort_state);
 
 		if (node->bounded)
 		{
@@ -479,7 +525,7 @@ ExecIncrementalSort(PlanState *pstate)
 			 */
 			SO1_printf("Re-calling switchToPresortedPrefixMode() because n_fullsort_remaining is > 0 (%ld)\n",
 					node->n_fullsort_remaining);
-			switchToPresortedPrefixMode(node);
+			switchToPresortedPrefixMode(pstate);
 		}
 		else
 		{
@@ -602,7 +648,11 @@ ExecIncrementalSort(PlanState *pstate)
 
 				SO1_printf("Sorting fullsort with %ld tuples\n", nTuples);
 				tuplesort_performsort(fullsort_state);
-				node->fullsort_group_count++;
+
+				if (pstate->instrument != NULL)
+					instrumentSortedGroup(pstate,
+							&node->incsort_info.fullsortGroupInfo,
+							fullsort_state);
 
 				SO_printf("Setting execution_status to INCSORT_READFULLSORT (final tuple) \n");
 				node->execution_status = INCSORT_READFULLSORT;
@@ -673,7 +723,12 @@ ExecIncrementalSort(PlanState *pstate)
 					 */
 					SO1_printf("Sorting fullsort tuplesort with %ld tuples\n", nTuples);
 					tuplesort_performsort(fullsort_state);
-					node->fullsort_group_count++;
+
+					if (pstate->instrument != NULL)
+						instrumentSortedGroup(pstate,
+								&node->incsort_info.fullsortGroupInfo,
+								fullsort_state);
+
 					SO_printf("Setting execution_status to INCSORT_READFULLSORT (found end of group)\n");
 					node->execution_status = INCSORT_READFULLSORT;
 					break;
@@ -705,7 +760,10 @@ ExecIncrementalSort(PlanState *pstate)
 				 */
 				SO1_printf("Sorting fullsort tuplesort with %ld tuples\n", nTuples);
 				tuplesort_performsort(fullsort_state);
-				node->fullsort_group_count++;
+				if (pstate->instrument != NULL)
+					instrumentSortedGroup(pstate,
+							&node->incsort_info.fullsortGroupInfo,
+							fullsort_state);
 
 				/*
 				 * If the full sort tuplesort happened to switch into top-n heapsort mode
@@ -735,7 +793,7 @@ ExecIncrementalSort(PlanState *pstate)
 				node->n_fullsort_remaining = nTuples;
 
 				/* Transition the tuples to the presorted prefix tuplesort. */
-				switchToPresortedPrefixMode(node);
+				switchToPresortedPrefixMode(pstate);
 
 				/*
 				 * Since we know we had tuples to move to the presorted prefix
@@ -801,7 +859,12 @@ ExecIncrementalSort(PlanState *pstate)
 		/* Perform the sort and return the tuples to the inner plan nodes. */
 		SO1_printf("Sorting presorted prefix tuplesort with >= %ld tuples\n", nTuples);
 		tuplesort_performsort(node->prefixsort_state);
-		node->prefixsort_group_count++;
+
+		if (pstate->instrument != NULL)
+			instrumentSortedGroup(pstate,
+					&node->incsort_info.prefixsortGroupInfo,
+					node->prefixsort_state);
+
 		SO_printf("Setting execution_status to INCSORT_READPREFIXSORT (found end of group)\n");
 		node->execution_status = INCSORT_READPREFIXSORT;
 
@@ -828,26 +891,6 @@ ExecIncrementalSort(PlanState *pstate)
 	 */
 	node->sort_Done = true;
 
-	/* Record shared stats if we're a parallel worker. */
-	if (node->shared_info && node->am_worker)
-	{
-		IncrementalSortInfo *incsort_info =
-			&node->shared_info->sinfo[ParallelWorkerNumber];
-
-		Assert(IsParallelWorker());
-		Assert(ParallelWorkerNumber <= node->shared_info->num_workers);
-
-		tuplesort_get_stats(fullsort_state, &incsort_info->fullsort_instrument);
-		incsort_info->fullsort_group_count = node->fullsort_group_count;
-
-		if (node->prefixsort_state)
-		{
-			tuplesort_get_stats(node->prefixsort_state,
-					&incsort_info->prefixsort_instrument);
-			incsort_info->prefixsort_group_count = node->prefixsort_group_count;
-		}
-	}
-
 	/*
 	 * Get the first or next tuple from tuplesort. Returns NULL if no more
 	 * tuples.
@@ -900,10 +943,28 @@ ExecInitIncrementalSort(IncrementalSort *node, EState *estate, int eflags)
 	incrsortstate->transfer_tuple = NULL;
 	incrsortstate->n_fullsort_remaining = 0;
 	incrsortstate->bound_Done = 0;
-	incrsortstate->fullsort_group_count = 0;
-	incrsortstate->prefixsort_group_count = 0;
 	incrsortstate->presorted_keys = NULL;
 
+	if (incrsortstate->ss.ps.instrument != NULL)
+	{
+		IncrementalSortGroupInfo *fullsortGroupInfo =
+			&incrsortstate->incsort_info.fullsortGroupInfo;
+		IncrementalSortGroupInfo *prefixsortGroupInfo =
+			&incrsortstate->incsort_info.prefixsortGroupInfo;
+		fullsortGroupInfo->groupCount = 0;
+		fullsortGroupInfo->maxDiskSpaceUsed = 0;
+		fullsortGroupInfo->totalDiskSpaceUsed = 0;
+		fullsortGroupInfo->maxMemorySpaceUsed = 0;
+		fullsortGroupInfo->totalMemorySpaceUsed = 0;
+		fullsortGroupInfo->sortMethods = NIL;
+		prefixsortGroupInfo->groupCount = 0;
+		prefixsortGroupInfo->maxDiskSpaceUsed = 0;
+		prefixsortGroupInfo->totalDiskSpaceUsed = 0;
+		prefixsortGroupInfo->maxMemorySpaceUsed = 0;
+		prefixsortGroupInfo->totalMemorySpaceUsed = 0;
+		prefixsortGroupInfo->sortMethods = NIL;
+	}
+
 	/*
 	 * Miscellaneous initialization
 	 *
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index f905e384a2..0934482123 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -2022,18 +2022,26 @@ typedef struct SortState
 	SharedSortInfo *shared_info;	/* one entry per worker */
 } SortState;
 
-/* ----------------
- *	 Shared memory container for per-worker incremental sort information
- * ----------------
- */
+typedef struct IncrementalSortGroupInfo
+{
+	int64 groupCount;
+	long maxDiskSpaceUsed;
+	long totalDiskSpaceUsed;
+	long maxMemorySpaceUsed;
+	long totalMemorySpaceUsed;
+	List *sortMethods;
+} IncrementalSortGroupInfo;
+
 typedef struct IncrementalSortInfo
 {
-	TuplesortInstrumentation	fullsort_instrument;
-	int64						fullsort_group_count;
-	TuplesortInstrumentation	prefixsort_instrument;
-	int64						prefixsort_group_count;
+	IncrementalSortGroupInfo fullsortGroupInfo;
+	IncrementalSortGroupInfo prefixsortGroupInfo;
 } IncrementalSortInfo;
 
+/* ----------------
+ *	 Shared memory container for per-worker incremental sort information
+ * ----------------
+ */
 typedef struct SharedIncrementalSortInfo
 {
 	int							num_workers;
@@ -2067,8 +2075,9 @@ typedef struct IncrementalSortState
 	Tuplesortstate	   *prefixsort_state; /* private state of tuplesort.c */
 	/* the keys by which the input path is already sorted */
 	PresortedKeyData *presorted_keys;
-	int64		fullsort_group_count;	/* number of groups with equal presorted keys */
-	int64		prefixsort_group_count;	/* number of groups with equal presorted keys */
+
+	IncrementalSortInfo incsort_info;
+
 	/* slot for pivot tuple defining values of presorted keys within group */
 	TupleTableSlot *group_pivot;
 	TupleTableSlot *transfer_tuple;
diff --git a/src/test/regress/expected/incremental_sort.out b/src/test/regress/expected/incremental_sort.out
index 3a58efdf91..7892b111d7 100644
--- a/src/test/regress/expected/incremental_sort.out
+++ b/src/test/regress/expected/incremental_sort.out
@@ -436,6 +436,82 @@ select * from (select * from t order by a) s order by a, b limit 55;
  2 | 55
 (55 rows)
 
+-- Test EXPLAIN ANALYZE (text output) with only a fullsort group.
+explain (analyze, costs off, summary off, timing off)
+select * from (select * from t order by a) s order by a, b limit 55;
+                                           QUERY PLAN                                            
+-------------------------------------------------------------------------------------------------
+ Limit (actual rows=55 loops=1)
+   ->  Incremental Sort (actual rows=55 loops=1)
+         Sort Key: t.a, t.b
+         Presorted Key: t.a
+         Full-sort Groups: 2 (Methods: quicksort, top-N heapsort) Memory: 27kB (avg), 27kB (max)
+         ->  Sort (actual rows=100 loops=1)
+               Sort Key: t.a
+               Sort Method: quicksort  Memory: 30kB
+               ->  Seq Scan on t (actual rows=100 loops=1)
+(9 rows)
+
+explain (analyze, costs off, summary off, timing off, format json)
+select * from (select * from t order by a) s order by a, b limit 55;
+                            QUERY PLAN                             
+-------------------------------------------------------------------
+ [                                                                +
+   {                                                              +
+     "Plan": {                                                    +
+       "Node Type": "Limit",                                      +
+       "Parallel Aware": false,                                   +
+       "Actual Rows": 55,                                         +
+       "Actual Loops": 1,                                         +
+       "Plans": [                                                 +
+         {                                                        +
+           "Node Type": "Incremental Sort",                       +
+           "Parent Relationship": "Outer",                        +
+           "Parallel Aware": false,                               +
+           "Actual Rows": 55,                                     +
+           "Actual Loops": 1,                                     +
+           "Sort Key": ["t.a", "t.b"],                            +
+           "Presorted Key": ["t.a"],                              +
+           "Full-sort Groups": {                                  +
+             "Group Count": 2,                                    +
+             "Sort Methods Used": ["quicksort", "top-N heapsort"],+
+             "Average Sort Space Used": 27,                       +
+             "Maximum Sort Space Used": 27,                       +
+             "Sort Space Type": "Memory"                          +
+           },                                                     +
+           "Plans": [                                             +
+             {                                                    +
+               "Node Type": "Sort",                               +
+               "Parent Relationship": "Outer",                    +
+               "Parallel Aware": false,                           +
+               "Actual Rows": 100,                                +
+               "Actual Loops": 1,                                 +
+               "Sort Key": ["t.a"],                               +
+               "Sort Method": "quicksort",                        +
+               "Sort Space Used": 30,                             +
+               "Sort Space Type": "Memory",                       +
+               "Plans": [                                         +
+                 {                                                +
+                   "Node Type": "Seq Scan",                       +
+                   "Parent Relationship": "Outer",                +
+                   "Parallel Aware": false,                       +
+                   "Relation Name": "t",                          +
+                   "Alias": "t",                                  +
+                   "Actual Rows": 100,                            +
+                   "Actual Loops": 1                              +
+                 }                                                +
+               ]                                                  +
+             }                                                    +
+           ]                                                      +
+         }                                                        +
+       ]                                                          +
+     },                                                           +
+     "Triggers": [                                                +
+     ]                                                            +
+   }                                                              +
+ ]
+(1 row)
+
 delete from t;
 -- An initial small group followed by a large group.
 insert into t(a, b) select (case when i < 5 then i else 9 end), i from generate_series(1, 100) n(i);
@@ -526,6 +602,90 @@ select * from (select * from t order by a) s order by a, b limit 70;
  9 | 70
 (70 rows)
 
+-- Test EXPLAIN ANALYZE (text output) with both fullsort and presorted groups.
+explain (analyze, costs off, summary off, timing off)
+select * from (select * from t order by a) s order by a, b limit 70;
+                                   QUERY PLAN                                    
+---------------------------------------------------------------------------------
+ Limit (actual rows=70 loops=1)
+   ->  Incremental Sort (actual rows=70 loops=1)
+         Sort Key: t.a, t.b
+         Presorted Key: t.a
+         Full-sort Groups: 1 (Methods: quicksort) Memory: 28kB (avg), 28kB (max)
+         Presorted Groups: 5 (Methods: quicksort) Memory: 26kB (avg), 30kB (max)
+         ->  Sort (actual rows=100 loops=1)
+               Sort Key: t.a
+               Sort Method: quicksort  Memory: 30kB
+               ->  Seq Scan on t (actual rows=100 loops=1)
+(10 rows)
+
+explain (analyze, costs off, summary off, timing off, format json)
+select * from (select * from t order by a) s order by a, b limit 70;
+                    QUERY PLAN                     
+---------------------------------------------------
+ [                                                +
+   {                                              +
+     "Plan": {                                    +
+       "Node Type": "Limit",                      +
+       "Parallel Aware": false,                   +
+       "Actual Rows": 70,                         +
+       "Actual Loops": 1,                         +
+       "Plans": [                                 +
+         {                                        +
+           "Node Type": "Incremental Sort",       +
+           "Parent Relationship": "Outer",        +
+           "Parallel Aware": false,               +
+           "Actual Rows": 70,                     +
+           "Actual Loops": 1,                     +
+           "Sort Key": ["t.a", "t.b"],            +
+           "Presorted Key": ["t.a"],              +
+           "Full-sort Groups": {                  +
+             "Group Count": 1,                    +
+             "Sort Methods Used": ["quicksort"],  +
+             "Average Sort Space Used": 28,       +
+             "Maximum Sort Space Used": 28,       +
+             "Sort Space Type": "Memory"          +
+           },                                     +
+           "Presorted Groups": {                  +
+             "Group Count": 5,                    +
+             "Sort Methods Used": ["quicksort"],  +
+             "Average Sort Space Used": 26,       +
+             "Maximum Sort Space Used": 30,       +
+             "Sort Space Type": "Memory"          +
+           },                                     +
+           "Plans": [                             +
+             {                                    +
+               "Node Type": "Sort",               +
+               "Parent Relationship": "Outer",    +
+               "Parallel Aware": false,           +
+               "Actual Rows": 100,                +
+               "Actual Loops": 1,                 +
+               "Sort Key": ["t.a"],               +
+               "Sort Method": "quicksort",        +
+               "Sort Space Used": 30,             +
+               "Sort Space Type": "Memory",       +
+               "Plans": [                         +
+                 {                                +
+                   "Node Type": "Seq Scan",       +
+                   "Parent Relationship": "Outer",+
+                   "Parallel Aware": false,       +
+                   "Relation Name": "t",          +
+                   "Alias": "t",                  +
+                   "Actual Rows": 100,            +
+                   "Actual Loops": 1              +
+                 }                                +
+               ]                                  +
+             }                                    +
+           ]                                      +
+         }                                        +
+       ]                                          +
+     },                                           +
+     "Triggers": [                                +
+     ]                                            +
+   }                                              +
+ ]
+(1 row)
+
 delete from t;
 -- Small groups of 10 tuples each tested around each mode transition point.
 insert into t(a, b) select i / 10, i from generate_series(1, 70) n(i);
diff --git a/src/test/regress/sql/incremental_sort.sql b/src/test/regress/sql/incremental_sort.sql
index b9df37412f..9320a10b91 100644
--- a/src/test/regress/sql/incremental_sort.sql
+++ b/src/test/regress/sql/incremental_sort.sql
@@ -39,12 +39,22 @@ delete from t;
 insert into t(a, b) select (case when i < 50 then 1 else 2 end), i from generate_series(1, 100) n(i);
 explain (costs off) select * from (select * from t order by a) s order by a, b limit 55;
 select * from (select * from t order by a) s order by a, b limit 55;
+-- Test EXPLAIN ANALYZE (text output) with only a fullsort group.
+explain (analyze, costs off, summary off, timing off)
+select * from (select * from t order by a) s order by a, b limit 55;
+explain (analyze, costs off, summary off, timing off, format json)
+select * from (select * from t order by a) s order by a, b limit 55;
 delete from t;
 
 -- An initial small group followed by a large group.
 insert into t(a, b) select (case when i < 5 then i else 9 end), i from generate_series(1, 100) n(i);
 explain (costs off) select * from (select * from t order by a) s order by a, b limit 70;
 select * from (select * from t order by a) s order by a, b limit 70;
+-- Test EXPLAIN ANALYZE (text output) with both fullsort and presorted groups.
+explain (analyze, costs off, summary off, timing off)
+select * from (select * from t order by a) s order by a, b limit 70;
+explain (analyze, costs off, summary off, timing off, format json)
+select * from (select * from t order by a) s order by a, b limit 70;
 delete from t;
 
 -- Small groups of 10 tuples each tested around each mode transition point.
-- 
2.21.1

