I added metrics for the validate_index phases; patch attached.  This is
still a bit raw, but it looks much better now.  Here's a sample
concurrent index build on a 100M tuple table.  There are no concurrent
transactions, so phases that wait for lockers don't appear.  I'm not
making any effort to report metrics during phase 6, note.  In phase 7
I'm currently reporting only tuple counts; I think it'd be better to
report block numbers.  

I don't show it here, but when an index is built on a partitioned table,
the "partitions done" number goes up all the way to "partitions total"
and the phases come and go for each partition.

One thing is clear: given the phase mechanics of varying durations, it
seems hard to use these numbers to predict total index build time.


     now      |                         phase                          | blocks 
total | blocks done | tuples total | tuples done | partitions total | 
partitions done 
--------------+--------------------------------------------------------+--------------+-------------+--------------+-------------+------------------+-----------------
 15:56:33.792 | building index (3 of 8): initializing (1/5)            |       
442478 |          32 |            0 |           0 |                0 |          
     0
 15:56:33.805 | building index (3 of 8): initializing (1/5)            |       
442478 |         188 |            0 |           0 |                0 |          
     0
     [snip about 500 lines]
 15:56:41.345 | building index (3 of 8): initializing (1/5)            |       
442478 |      439855 |            0 |           0 |                0 |          
     0
 15:56:41.356 | building index (3 of 8): initializing (1/5)            |       
442478 |      440288 |            0 |           0 |                0 |          
     0
 15:56:41.367 | building index (3 of 8): initializing (1/5)            |       
442478 |      440778 |            0 |           0 |                0 |          
     0
 15:56:41.378 | building index (3 of 8): initializing (1/5)            |       
442478 |      441706 |            0 |           0 |                0 |          
     0
 15:56:41.389 | building index (3 of 8): initializing (1/5)            |       
442478 |      442399 |            0 |           0 |                0 |          
     0
 15:56:41.4   | building index (3 of 8): initializing (1/5)            |       
442478 |      442399 |            0 |           0 |                0 |          
     0

     [snip 300 lines]           ... I'm not sure what happened in this 3 
seconds period.  No metrics were being updated.

 15:56:44.65  | building index (3 of 8): initializing (1/5)            |       
442478 |      442399 |            0 |           0 |                0 |          
     0
 15:56:44.661 | building index (3 of 8): initializing (1/5)            |       
442478 |      442399 |            0 |           0 |                0 |          
     0
 15:56:44.672 | building index (3 of 8): initializing (1/5)            |       
442478 |      442399 |            0 |           0 |                0 |          
     0
 15:56:44.682 | building index (3 of 8): initializing (1/5)            |       
442478 |      442399 |            0 |           0 |                0 |          
     0
 15:56:44.694 | building index (3 of 8): initializing (1/5)            |       
442478 |      442399 |            0 |           0 |                0 |          
     0
 15:56:44.705 | building index (3 of 8): sorting tuples, spool 1 (3/5) |       
442478 |      442399 |    100000000 |           0 |                0 |          
     0
 15:56:44.716 | building index (3 of 8): sorting tuples, spool 1 (3/5) |       
442478 |      442399 |    100000000 |           0 |                0 |          
     0
 15:56:44.727 | building index (3 of 8): final btree sort & load (5/5) |       
442478 |      442399 |    100000000 |       79057 |                0 |          
     0
 15:56:44.738 | building index (3 of 8): final btree sort & load (5/5) |       
442478 |      442399 |    100000000 |      217018 |                0 |          
     0
 15:56:44.75  | building index (3 of 8): final btree sort & load (5/5) |       
442478 |      442399 |    100000000 |      353804 |                0 |          
     0
 15:56:44.761 | building index (3 of 8): final btree sort & load (5/5) |       
442478 |      442399 |    100000000 |      487892 |                0 |          
     0
 15:56:44.773 | building index (3 of 8): final btree sort & load (5/5) |       
442478 |      442399 |    100000000 |      634136 |                0 |          
     0
    [snip 660 lines]
 15:56:52.47  | building index (3 of 8): final btree sort & load (5/5) |       
442478 |      442399 |    100000000 |    99111337 |                0 |          
     0
 15:56:52.482 | building index (3 of 8): final btree sort & load (5/5) |       
442478 |      442399 |    100000000 |    99285701 |                0 |          
     0
 15:56:52.493 | building index (3 of 8): final btree sort & load (5/5) |       
442478 |      442399 |    100000000 |    99444763 |                0 |          
     0
 15:56:52.505 | building index (3 of 8): final btree sort & load (5/5) |       
442478 |      442399 |    100000000 |    99612313 |                0 |          
     0
 15:56:52.516 | building index (3 of 8): final btree sort & load (5/5) |       
442478 |      442399 |    100000000 |    99782666 |                0 |          
     0
 15:56:52.528 | building index (3 of 8): final btree sort & load (5/5) |       
442478 |      442399 |    100000000 |    99937524 |                0 |          
     0
 15:56:52.54  | building index (3 of 8): final btree sort & load (5/5) |       
442478 |      442399 |    100000000 |   100000000 |                0 |          
     0
 15:56:52.551 | building index (3 of 8): final btree sort & load (5/5) |       
442478 |      442399 |    100000000 |   100000000 |                0 |          
     0
 15:56:52.561 | building index (3 of 8): final btree sort & load (5/5) |       
442478 |      442399 |    100000000 |   100000000 |                0 |          
     0
 15:56:52.572 | building index (3 of 8): final btree sort & load (5/5) |       
442478 |      442399 |    100000000 |   100000000 |                0 |          
     0
 15:56:52.583 | building index (3 of 8): final btree sort & load (5/5) |       
442478 |      442399 |    100000000 |   100000000 |                0 |          
     0
 15:56:52.594 | building index (3 of 8): final btree sort & load (5/5) |       
442478 |      442399 |    100000000 |   100000000 |                0 |          
     0
 15:56:52.604 | building index (3 of 8): final btree sort & load (5/5) |       
442478 |      442399 |    100000000 |   100000000 |                0 |          
     0
 15:56:52.615 | building index (3 of 8): final btree sort & load (5/5) |       
442478 |      442399 |    100000000 |   100000000 |                0 |          
     0
 15:56:52.626 | building index (3 of 8): final btree sort & load (5/5) |       
442478 |      442399 |    100000000 |   100000000 |                0 |          
     0
 15:56:52.637 | building index (3 of 8): final btree sort & load (5/5) |       
442478 |      442399 |    100000000 |   100000000 |                0 |          
     0
 15:56:52.648 | building index (3 of 8): final btree sort & load (5/5) |       
442478 |      442399 |    100000000 |   100000000 |                0 |          
     0
 15:56:52.658 | building index (3 of 8): final btree sort & load (5/5) |       
442478 |      442399 |    100000000 |   100000000 |                0 |          
     0
 15:56:52.669 | building index (3 of 8): final btree sort & load (5/5) |       
442478 |      442399 |    100000000 |   100000000 |                0 |          
     0
 15:56:52.68  | building index (3 of 8): final btree sort & load (5/5) |       
442478 |      442399 |    100000000 |   100000000 |                0 |          
     0
 15:56:52.691 | building index (3 of 8): final btree sort & load (5/5) |       
442478 |      442399 |    100000000 |   100000000 |                0 |          
     0
 15:56:52.701 | building index (3 of 8): final btree sort & load (5/5) |       
442478 |      442399 |    100000000 |   100000000 |                0 |          
     0
 15:56:52.712 | building index (3 of 8): final btree sort & load (5/5) |       
442478 |      442399 |    100000000 |   100000000 |                0 |          
     0
 15:56:52.723 | validating index scan (phase 5 of 8)                   |       
274194 |        1049 |            0 |           0 |                0 |          
     0
 15:56:52.734 | validating index scan (phase 5 of 8)                   |       
274194 |        2676 |            0 |           0 |                0 |          
     0
 15:56:52.744 | validating index scan (phase 5 of 8)                   |       
274194 |        2876 |            0 |           0 |                0 |          
     0
 15:56:52.755 | validating index scan (phase 5 of 8)                   |       
274194 |        4194 |            0 |           0 |                0 |          
     0
    [snip 400 lines]
 15:56:57.031 | validating index scan (phase 5 of 8)                   |       
274194 |      268343 |            0 |           0 |                0 |          
     0
 15:56:57.042 | validating index scan (phase 5 of 8)                   |       
274194 |      268479 |            0 |           0 |                0 |          
     0
 15:56:57.053 | validating index scan (phase 5 of 8)                   |       
274194 |      270027 |            0 |           0 |                0 |          
     0
 15:56:57.064 | validating index scan (phase 5 of 8)                   |       
274194 |      271580 |            0 |           0 |                0 |          
     0
 15:56:57.075 | validating index scan (phase 5 of 8)                   |       
274194 |      273121 |            0 |           0 |                0 |          
     0
 15:56:57.085 | sorting index scan results (phase 6 of 8)              |       
274194 |      274193 |            0 |           0 |                0 |          
     0
 15:56:57.096 | sorting index scan results (phase 6 of 8)              |       
274194 |      274193 |            0 |           0 |                0 |          
     0
 15:56:57.107 | sorting index scan results (phase 6 of 8)              |       
274194 |      274193 |            0 |           0 |                0 |          
     0
 15:56:57.118 | sorting index scan results (phase 6 of 8)              |       
274194 |      274193 |            0 |           0 |                0 |          
     0
 15:56:57.128 | sorting index scan results (phase 6 of 8)              |       
274194 |      274193 |            0 |           0 |                0 |          
     0
 15:56:57.139 | sorting index scan results (phase 6 of 8)              |       
274194 |      274193 |            0 |           0 |                0 |          
     0
 15:56:57.15  | sorting index scan results (phase 6 of 8)              |       
274194 |      274193 |            0 |           0 |                0 |          
     0
 15:56:57.161 | validate index heapscan (phase 7 of 8)                 |        
    0 |           0 |    100000000 |       50152 |                0 |           
    0
 15:56:57.172 | validate index heapscan (phase 7 of 8)                 |        
    0 |           0 |    100000000 |      175602 |                0 |           
    0
 15:56:57.182 | validate index heapscan (phase 7 of 8)                 |        
    0 |           0 |    100000000 |      305326 |                0 |           
    0
 15:56:57.193 | validate index heapscan (phase 7 of 8)                 |        
    0 |           0 |    100000000 |      430142 |                0 |           
    0
      [snip 730 lines]
 15:57:05.003 | validate index heapscan (phase 7 of 8)                 |        
    0 |           0 |    100000000 |    99125556 |                0 |           
    0
 15:57:05.013 | validate index heapscan (phase 7 of 8)                 |        
    0 |           0 |    100000000 |    99276471 |                0 |           
    0
 15:57:05.024 | validate index heapscan (phase 7 of 8)                 |        
    0 |           0 |    100000000 |    99425041 |                0 |           
    0
 15:57:05.035 | validate index heapscan (phase 7 of 8)                 |        
    0 |           0 |    100000000 |    99580174 |                0 |           
    0
 15:57:05.045 | validate index heapscan (phase 7 of 8)                 |        
    0 |           0 |    100000000 |    99720505 |                0 |           
    0
 15:57:05.056 | validate index heapscan (phase 7 of 8)                 |        
    0 |           0 |    100000000 |    99862311 |                0 |           
    0
 15:57:05.067 | validate index heapscan (phase 7 of 8)                 |        
    0 |           0 |    100000000 |   100000000 |                0 |           
    0
 15:57:05.077 | validate index heapscan (phase 7 of 8)                 |        
    0 |           0 |    100000000 |   100000000 |                0 |           
    0
 15:57:05.088 | validate index heapscan (phase 7 of 8)                 |        
    0 |           0 |    100000000 |   100000000 |                0 |           
    0
 15:57:05.099 | validate index heapscan (phase 7 of 8)                 |        
    0 |           0 |    100000000 |   100000000 |                0 |           
    0
 15:57:05.109 | validate index heapscan (phase 7 of 8)                 |        
    0 |           0 |    100000000 |   100000000 |                0 |           
    0
 15:57:05.12  | validate index heapscan (phase 7 of 8)                 |        
    0 |           0 |    100000000 |   100000000 |                0 |           
    0
 15:57:05.131 | validate index heapscan (phase 7 of 8)                 |        
    0 |           0 |    100000000 |   100000000 |                0 |           
    0
 15:57:05.142 | validate index heapscan (phase 7 of 8)                 |        
    0 |           0 |    100000000 |   100000000 |                0 |           
    0
 15:57:05.152 | validate index heapscan (phase 7 of 8)                 |        
    0 |           0 |    100000000 |   100000000 |                0 |           
    0
 15:57:05.163 | validate index heapscan (phase 7 of 8)                 |        
    0 |           0 |    100000000 |   100000000 |                0 |           
    0
 15:57:05.174 | validate index heapscan (phase 7 of 8)                 |        
    0 |           0 |    100000000 |   100000000 |                0 |           
    0
(2862 filas)

and then it's done.

-- 
Álvaro Herrera                https://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
diff --git a/contrib/amcheck/verify_nbtree.c b/contrib/amcheck/verify_nbtree.c
index 964200a7678..99f6ed6bc44 100644
--- a/contrib/amcheck/verify_nbtree.c
+++ b/contrib/amcheck/verify_nbtree.c
@@ -534,7 +534,7 @@ bt_check_every_level(Relation rel, Relation heaprel, bool readonly,
 			 RelationGetRelationName(state->rel),
 			 RelationGetRelationName(state->heaprel));
 
-		IndexBuildHeapScan(state->heaprel, state->rel, indexinfo, true,
+		IndexBuildHeapScan(state->heaprel, state->rel, indexinfo, true, false,
 						   bt_tuple_present_callback, (void *) state, scan);
 
 		ereport(DEBUG1,
diff --git a/contrib/bloom/blinsert.c b/contrib/bloom/blinsert.c
index e43fbe0005f..947ee74881f 100644
--- a/contrib/bloom/blinsert.c
+++ b/contrib/bloom/blinsert.c
@@ -141,7 +141,7 @@ blbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	initCachedPage(&buildstate);
 
 	/* Do the heap scan */
-	reltuples = IndexBuildHeapScan(heap, index, indexInfo, true,
+	reltuples = IndexBuildHeapScan(heap, index, indexInfo, true, false,
 								   bloomBuildCallback, (void *) &buildstate,
 								   NULL);
 
diff --git a/contrib/bloom/blutils.c b/contrib/bloom/blutils.c
index 64583765787..79fe413f994 100644
--- a/contrib/bloom/blutils.c
+++ b/contrib/bloom/blutils.c
@@ -132,6 +132,7 @@ blhandler(PG_FUNCTION_ARGS)
 	amroutine->amcostestimate = blcostestimate;
 	amroutine->amoptions = bloptions;
 	amroutine->amproperty = NULL;
+	amroutine->amphasename = NULL;
 	amroutine->amvalidate = blvalidate;
 	amroutine->ambeginscan = blbeginscan;
 	amroutine->amrescan = blrescan;
diff --git a/doc/src/sgml/indexam.sgml b/doc/src/sgml/indexam.sgml
index 05102724ead..189179a5667 100644
--- a/doc/src/sgml/indexam.sgml
+++ b/doc/src/sgml/indexam.sgml
@@ -127,6 +127,7 @@ typedef struct IndexAmRoutine
     amcostestimate_function amcostestimate;
     amoptions_function amoptions;
     amproperty_function amproperty;     /* can be NULL */
+    amphasename_function amphasename;   /* can be NULL */
     amvalidate_function amvalidate;
     ambeginscan_function ambeginscan;
     amrescan_function amrescan;
@@ -468,6 +469,16 @@ amproperty (Oid index_oid, int attno,
 
   <para>
 <programlisting>
+char *
+amphasename (int64 phasenum);
+</programlisting>
+   Return the textual name of the given phase number.  The phase numbers are
+   those reported during an index build via the
+   <function>pgstat_progress_update_param</function> interface.
+  </para>
+
+  <para>
+<programlisting>
 bool
 amvalidate (Oid opclassoid);
 </programlisting>
diff --git a/src/backend/access/brin/brin.c b/src/backend/access/brin/brin.c
index 8f008dd0080..547a67fd2e6 100644
--- a/src/backend/access/brin/brin.c
+++ b/src/backend/access/brin/brin.c
@@ -111,6 +111,7 @@ brinhandler(PG_FUNCTION_ARGS)
 	amroutine->amcostestimate = brincostestimate;
 	amroutine->amoptions = brinoptions;
 	amroutine->amproperty = NULL;
+	amroutine->amphasename = NULL;
 	amroutine->amvalidate = brinvalidate;
 	amroutine->ambeginscan = brinbeginscan;
 	amroutine->amrescan = brinrescan;
@@ -718,7 +719,7 @@ brinbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * Now scan the relation.  No syncscan allowed here because we want the
 	 * heap blocks in physical order.
 	 */
-	reltuples = IndexBuildHeapScan(heap, index, indexInfo, false,
+	reltuples = IndexBuildHeapScan(heap, index, indexInfo, false, false,
 								   brinbuildCallback, (void *) state, NULL);
 
 	/* process the final batch */
@@ -1234,7 +1235,7 @@ summarize_range(IndexInfo *indexInfo, BrinBuildState *state, Relation heapRel,
 	 * by transactions that are still in progress, among other corner cases.
 	 */
 	state->bs_currRangeStart = heapBlk;
-	IndexBuildHeapRangeScan(heapRel, state->bs_irel, indexInfo, false, true,
+	IndexBuildHeapRangeScan(heapRel, state->bs_irel, indexInfo, false, true, false,
 							heapBlk, scanNumBlks,
 							brinbuildCallback, (void *) state, NULL);
 
diff --git a/src/backend/access/gin/gininsert.c b/src/backend/access/gin/gininsert.c
index 524ac5be8b5..838de4c1ec3 100644
--- a/src/backend/access/gin/gininsert.c
+++ b/src/backend/access/gin/gininsert.c
@@ -394,7 +394,7 @@ ginbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	 * Do the heap scan.  We disallow sync scan here because dataPlaceToPage
 	 * prefers to receive tuples in TID order.
 	 */
-	reltuples = IndexBuildHeapScan(heap, index, indexInfo, false,
+	reltuples = IndexBuildHeapScan(heap, index, indexInfo, false, false,
 								   ginBuildCallback, (void *) &buildstate, NULL);
 
 	/* dump remaining entries to the index */
diff --git a/src/backend/access/gin/ginutil.c b/src/backend/access/gin/ginutil.c
index afc20232ace..240f8df5ad8 100644
--- a/src/backend/access/gin/ginutil.c
+++ b/src/backend/access/gin/ginutil.c
@@ -64,6 +64,7 @@ ginhandler(PG_FUNCTION_ARGS)
 	amroutine->amcostestimate = gincostestimate;
 	amroutine->amoptions = ginoptions;
 	amroutine->amproperty = NULL;
+	amroutine->amphasename = NULL;
 	amroutine->amvalidate = ginvalidate;
 	amroutine->ambeginscan = ginbeginscan;
 	amroutine->amrescan = ginrescan;
diff --git a/src/backend/access/gist/gist.c b/src/backend/access/gist/gist.c
index b75b3a8dacd..8839b24d7ac 100644
--- a/src/backend/access/gist/gist.c
+++ b/src/backend/access/gist/gist.c
@@ -87,6 +87,7 @@ gisthandler(PG_FUNCTION_ARGS)
 	amroutine->amcostestimate = gistcostestimate;
 	amroutine->amoptions = gistoptions;
 	amroutine->amproperty = gistproperty;
+	amroutine->amphasename = NULL;
 	amroutine->amvalidate = gistvalidate;
 	amroutine->ambeginscan = gistbeginscan;
 	amroutine->amrescan = gistrescan;
diff --git a/src/backend/access/gist/gistbuild.c b/src/backend/access/gist/gistbuild.c
index bd142a3560d..015f874cc93 100644
--- a/src/backend/access/gist/gistbuild.c
+++ b/src/backend/access/gist/gistbuild.c
@@ -204,7 +204,7 @@ gistbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	/*
 	 * Do the heap scan.
 	 */
-	reltuples = IndexBuildHeapScan(heap, index, indexInfo, true,
+	reltuples = IndexBuildHeapScan(heap, index, indexInfo, true, false,
 								   gistBuildCallback, (void *) &buildstate, NULL);
 
 	/*
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index f1f01a0956d..b661daed381 100644
--- a/src/backend/access/hash/hash.c
+++ b/src/backend/access/hash/hash.c
@@ -82,6 +82,7 @@ hashhandler(PG_FUNCTION_ARGS)
 	amroutine->amcostestimate = hashcostestimate;
 	amroutine->amoptions = hashoptions;
 	amroutine->amproperty = NULL;
+	amroutine->amphasename = NULL;
 	amroutine->amvalidate = hashvalidate;
 	amroutine->ambeginscan = hashbeginscan;
 	amroutine->amrescan = hashrescan;
@@ -159,7 +160,7 @@ hashbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 	buildstate.heapRel = heap;
 
 	/* do the heap scan */
-	reltuples = IndexBuildHeapScan(heap, index, indexInfo, true,
+	reltuples = IndexBuildHeapScan(heap, index, indexInfo, true, false,
 								   hashbuildCallback, (void *) &buildstate, NULL);
 
 	if (buildstate.spool)
diff --git a/src/backend/access/nbtree/nbtree.c b/src/backend/access/nbtree/nbtree.c
index 98917de2efd..3a3fcaaf33d 100644
--- a/src/backend/access/nbtree/nbtree.c
+++ b/src/backend/access/nbtree/nbtree.c
@@ -22,6 +22,7 @@
 #include "access/nbtxlog.h"
 #include "access/relscan.h"
 #include "access/xlog.h"
+#include "commands/progress.h"
 #include "commands/vacuum.h"
 #include "miscadmin.h"
 #include "nodes/execnodes.h"
@@ -133,6 +134,7 @@ bthandler(PG_FUNCTION_ARGS)
 	amroutine->amcostestimate = btcostestimate;
 	amroutine->amoptions = btoptions;
 	amroutine->amproperty = btproperty;
+	amroutine->amphasename = btphasename;
 	amroutine->amvalidate = btvalidate;
 	amroutine->ambeginscan = btbeginscan;
 	amroutine->amrescan = btrescan;
@@ -1021,6 +1023,10 @@ btvacuumscan(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 		if (needLock)
 			UnlockRelationForExtension(rel, ExclusiveLock);
 
+		if (info->report_progress)
+			pgstat_progress_update_param(PROGRESS_SCAN_BLOCKS_TOTAL,
+										 num_pages);
+
 		/* Quit if we've scanned the whole relation */
 		if (blkno >= num_pages)
 			break;
@@ -1028,6 +1034,9 @@ btvacuumscan(IndexVacuumInfo *info, IndexBulkDeleteResult *stats,
 		for (; blkno < num_pages; blkno++)
 		{
 			btvacuumpage(&vstate, blkno, blkno);
+			if (info->report_progress)
+				pgstat_progress_update_param(PROGRESS_SCAN_BLOCKS_DONE,
+											 blkno);
 		}
 	}
 
diff --git a/src/backend/access/nbtree/nbtsort.c b/src/backend/access/nbtree/nbtsort.c
index dc398e11867..5f6c9a5de78 100644
--- a/src/backend/access/nbtree/nbtsort.c
+++ b/src/backend/access/nbtree/nbtsort.c
@@ -65,6 +65,7 @@
 #include "access/xlog.h"
 #include "access/xloginsert.h"
 #include "catalog/index.h"
+#include "commands/progress.h"
 #include "miscadmin.h"
 #include "pgstat.h"
 #include "storage/smgr.h"
@@ -288,7 +289,8 @@ static double _bt_parallel_heapscan(BTBuildState *buildstate,
 static void _bt_leader_participate_as_worker(BTBuildState *buildstate);
 static void _bt_parallel_scan_and_sort(BTSpool *btspool, BTSpool *btspool2,
 						   BTShared *btshared, Sharedsort *sharedsort,
-						   Sharedsort *sharedsort2, int sortmem);
+						   Sharedsort *sharedsort2, int sortmem,
+						   bool progress);
 
 
 /*
@@ -469,14 +471,19 @@ _bt_spools_heapscan(Relation heap, Relation index, BTBuildState *buildstate,
 	}
 
 	/* Fill spool using either serial or parallel heap scan */
+	pgstat_progress_update_param(PROGRESS_CREATEIDX_SUBPHASE,
+								 PROGRESS_BTREE_PHASE_INDEXBUILD_HEAPSCAN);
 	if (!buildstate->btleader)
-		reltuples = IndexBuildHeapScan(heap, index, indexInfo, true,
+		reltuples = IndexBuildHeapScan(heap, index, indexInfo, true, true,
 									   _bt_build_callback, (void *) buildstate,
 									   NULL);
 	else
 		reltuples = _bt_parallel_heapscan(buildstate,
 										  &indexInfo->ii_BrokenHotChain);
 
+	pgstat_progress_update_param(PROGRESS_CREATEIDX_TUPLES_TOTAL,
+								 buildstate->indtuples);
+
 	/* okay, all heap tuples are spooled */
 	if (buildstate->spool2 && !buildstate->havedead)
 	{
@@ -525,9 +532,15 @@ _bt_leafbuild(BTSpool *btspool, BTSpool *btspool2)
 	}
 #endif							/* BTREE_BUILD_STATS */
 
+	pgstat_progress_update_param(PROGRESS_CREATEIDX_SUBPHASE,
+								 PROGRESS_BTREE_PHASE_PERFORMSORT_1);
 	tuplesort_performsort(btspool->sortstate);
 	if (btspool2)
+	{
+		pgstat_progress_update_param(PROGRESS_CREATEIDX_SUBPHASE,
+									 PROGRESS_BTREE_PHASE_PERFORMSORT_2);
 		tuplesort_performsort(btspool2->sortstate);
+	}
 
 	wstate.heap = btspool->heap;
 	wstate.index = btspool->index;
@@ -543,6 +556,8 @@ _bt_leafbuild(BTSpool *btspool, BTSpool *btspool2)
 	wstate.btws_pages_written = 0;
 	wstate.btws_zeropage = NULL;	/* until needed */
 
+	pgstat_progress_update_param(PROGRESS_CREATEIDX_SUBPHASE,
+								 PROGRESS_BTREE_PHASE_LEAF_LOAD);
 	_bt_load(&wstate, btspool, btspool2);
 }
 
@@ -1078,6 +1093,7 @@ _bt_load(BTWriteState *wstate, BTSpool *btspool, BTSpool *btspool2)
 				keysz = IndexRelationGetNumberOfKeyAttributes(wstate->index);
 	ScanKey		indexScanKey = NULL;
 	SortSupport sortKeys;
+	long		tuples_done = 0L;
 
 	if (merge)
 	{
@@ -1170,6 +1186,10 @@ _bt_load(BTWriteState *wstate, BTSpool *btspool, BTSpool *btspool2)
 				_bt_buildadd(wstate, state, itup2);
 				itup2 = tuplesort_getindextuple(btspool2->sortstate, true);
 			}
+
+			/* Report progress */
+			pgstat_progress_update_param(PROGRESS_CREATEIDX_TUPLES_DONE,
+										 ++tuples_done);
 		}
 		pfree(sortKeys);
 	}
@@ -1184,6 +1204,10 @@ _bt_load(BTWriteState *wstate, BTSpool *btspool, BTSpool *btspool2)
 				state = _bt_pagestate(wstate, 0);
 
 			_bt_buildadd(wstate, state, itup);
+
+			/* Report progress */
+			pgstat_progress_update_param(PROGRESS_CREATEIDX_TUPLES_DONE,
+										 ++tuples_done);
 		}
 	}
 
@@ -1317,6 +1341,8 @@ _bt_begin_parallel(BTBuildState *buildstate, bool isconcurrent, int request)
 	btshared->indtuples = 0.0;
 	btshared->brokenhotchain = false;
 	heap_parallelscan_initialize(&btshared->heapdesc, btspool->heap, snapshot);
+	pgstat_progress_update_param(PROGRESS_SCAN_BLOCKS_TOTAL,
+								 btshared->heapdesc.phs_nblocks);
 
 	/*
 	 * Store shared tuplesort-private state, for which we reserved space.
@@ -1493,7 +1519,7 @@ _bt_leader_participate_as_worker(BTBuildState *buildstate)
 	/* Perform work common to all participants */
 	_bt_parallel_scan_and_sort(leaderworker, leaderworker2, btleader->btshared,
 							   btleader->sharedsort, btleader->sharedsort2,
-							   sortmem);
+							   sortmem, true);
 
 #ifdef BTREE_BUILD_STATS
 	if (log_btree_build_stats)
@@ -1584,7 +1610,7 @@ _bt_parallel_build_main(dsm_segment *seg, shm_toc *toc)
 	/* Perform sorting of spool, and possibly a spool2 */
 	sortmem = maintenance_work_mem / btshared->scantuplesortstates;
 	_bt_parallel_scan_and_sort(btspool, btspool2, btshared, sharedsort,
-							   sharedsort2, sortmem);
+							   sharedsort2, sortmem, false);
 
 #ifdef BTREE_BUILD_STATS
 	if (log_btree_build_stats)
@@ -1613,7 +1639,7 @@ _bt_parallel_build_main(dsm_segment *seg, shm_toc *toc)
 static void
 _bt_parallel_scan_and_sort(BTSpool *btspool, BTSpool *btspool2,
 						   BTShared *btshared, Sharedsort *sharedsort,
-						   Sharedsort *sharedsort2, int sortmem)
+						   Sharedsort *sharedsort2, int sortmem, bool progress)
 {
 	SortCoordinate coordinate;
 	BTBuildState buildstate;
@@ -1672,7 +1698,7 @@ _bt_parallel_scan_and_sort(BTSpool *btspool, BTSpool *btspool2,
 	indexInfo->ii_Concurrent = btshared->isconcurrent;
 	scan = heap_beginscan_parallel(btspool->heap, &btshared->heapdesc);
 	reltuples = IndexBuildHeapScan(btspool->heap, btspool->index, indexInfo,
-								   true, _bt_build_callback,
+								   true, progress, _bt_build_callback,
 								   (void *) &buildstate, scan);
 
 	/*
diff --git a/src/backend/access/nbtree/nbtutils.c b/src/backend/access/nbtree/nbtutils.c
index 2c05fb5e451..dc47663c374 100644
--- a/src/backend/access/nbtree/nbtutils.c
+++ b/src/backend/access/nbtree/nbtutils.c
@@ -20,6 +20,7 @@
 #include "access/nbtree.h"
 #include "access/reloptions.h"
 #include "access/relscan.h"
+#include "commands/progress.h"
 #include "miscadmin.h"
 #include "utils/array.h"
 #include "utils/lsyscache.h"
@@ -2082,6 +2083,29 @@ btproperty(Oid index_oid, int attno,
 	}
 }
 
+/*
+ *	btphasename() -- Return name of phase, for index build progress report
+ */
+char *
+btphasename(int64 phasenum)
+{
+	switch (phasenum)
+	{
+		case PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE:
+			return "initializing (1/5)";
+		case PROGRESS_BTREE_PHASE_INDEXBUILD_HEAPSCAN:
+			return "table scan (2/5)";
+		case PROGRESS_BTREE_PHASE_PERFORMSORT_1:
+			return "sorting tuples, spool 1 (3/5)";
+		case PROGRESS_BTREE_PHASE_PERFORMSORT_2:
+			return "sorting tuples, spool 2 (4/5)";
+		case PROGRESS_BTREE_PHASE_LEAF_LOAD:
+			return "final btree sort & load (5/5)";
+		default:
+			return NULL;
+	}
+}
+
 /*
  *	_bt_nonkey_truncate() -- create tuple without non-key suffix attributes.
  *
diff --git a/src/backend/access/spgist/spginsert.c b/src/backend/access/spgist/spginsert.c
index f428a151385..1bc671c7238 100644
--- a/src/backend/access/spgist/spginsert.c
+++ b/src/backend/access/spgist/spginsert.c
@@ -142,7 +142,7 @@ spgbuild(Relation heap, Relation index, IndexInfo *indexInfo)
 											  "SP-GiST build temporary context",
 											  ALLOCSET_DEFAULT_SIZES);
 
-	reltuples = IndexBuildHeapScan(heap, index, indexInfo, true,
+	reltuples = IndexBuildHeapScan(heap, index, indexInfo, true, false,
 								   spgistBuildCallback, (void *) &buildstate,
 								   NULL);
 
diff --git a/src/backend/access/spgist/spgutils.c b/src/backend/access/spgist/spgutils.c
index 8e63c1fad25..9db2fb1693c 100644
--- a/src/backend/access/spgist/spgutils.c
+++ b/src/backend/access/spgist/spgutils.c
@@ -67,6 +67,7 @@ spghandler(PG_FUNCTION_ARGS)
 	amroutine->amcostestimate = spgcostestimate;
 	amroutine->amoptions = spgoptions;
 	amroutine->amproperty = spgproperty;
+	amroutine->amphasename = NULL;
 	amroutine->amvalidate = spgvalidate;
 	amroutine->ambeginscan = spgbeginscan;
 	amroutine->amrescan = spgrescan;
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index d16c3d0ea50..2da7a1c6166 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -49,8 +49,9 @@
 #include "catalog/pg_trigger.h"
 #include "catalog/pg_type.h"
 #include "catalog/storage.h"
-#include "commands/tablecmds.h"
 #include "commands/event_trigger.h"
+#include "commands/progress.h"
+#include "commands/tablecmds.h"
 #include "commands/trigger.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
@@ -58,6 +59,7 @@
 #include "nodes/nodeFuncs.h"
 #include "optimizer/optimizer.h"
 #include "parser/parser.h"
+#include "pgstat.h"
 #include "rewrite/rewriteManip.h"
 #include "storage/bufmgr.h"
 #include "storage/lmgr.h"
@@ -1597,7 +1599,7 @@ index_drop(Oid indexId, bool concurrent)
 		 * to acquire an exclusive lock on our table.  The lock code will
 		 * detect deadlock and error out properly.
 		 */
-		WaitForLockers(heaplocktag, AccessExclusiveLock);
+		WaitForLockers(heaplocktag, AccessExclusiveLock, true);
 
 		/*
 		 * No more predicate locks will be acquired on this index, and we're
@@ -1641,7 +1643,7 @@ index_drop(Oid indexId, bool concurrent)
 		 * Wait till every transaction that saw the old index state has
 		 * finished.
 		 */
-		WaitForLockers(heaplocktag, AccessExclusiveLock);
+		WaitForLockers(heaplocktag, AccessExclusiveLock, true);
 
 		/*
 		 * Re-open relations to allow us to complete our actions.
@@ -2291,6 +2293,25 @@ index_build(Relation heapRelation,
 						   save_sec_context | SECURITY_RESTRICTED_OPERATION);
 	save_nestlevel = NewGUCNestLevel();
 
+	/* Set up initial progress report status */
+	{
+		const int	index[] = {
+			PROGRESS_CREATEIDX_PHASE,
+			PROGRESS_CREATEIDX_SUBPHASE,
+			PROGRESS_CREATEIDX_TUPLES_DONE,
+			PROGRESS_CREATEIDX_TUPLES_TOTAL,
+			PROGRESS_SCAN_BLOCKS_DONE,
+			PROGRESS_SCAN_BLOCKS_TOTAL
+		};
+		const int64	val[] = {
+			PROGRESS_CREATEIDX_PHASE_BUILD,
+			PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE,
+			0, 0, 0, 0
+		};
+
+		pgstat_progress_update_multi_param(6, index, val);
+	}
+
 	/*
 	 * Call the access method's build procedure
 	 */
@@ -2428,13 +2449,14 @@ IndexBuildHeapScan(Relation heapRelation,
 				   Relation indexRelation,
 				   IndexInfo *indexInfo,
 				   bool allow_sync,
+				   bool progress,
 				   IndexBuildCallback callback,
 				   void *callback_state,
 				   HeapScanDesc scan)
 {
 	return IndexBuildHeapRangeScan(heapRelation, indexRelation,
 								   indexInfo, allow_sync,
-								   false,
+								   false, progress,
 								   0, InvalidBlockNumber,
 								   callback, callback_state, scan);
 }
@@ -2455,6 +2477,7 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 						IndexInfo *indexInfo,
 						bool allow_sync,
 						bool anyvisible,
+						bool progress,
 						BlockNumber start_blockno,
 						BlockNumber numblocks,
 						IndexBuildCallback callback,
@@ -2476,6 +2499,8 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 	TransactionId OldestXmin;
 	BlockNumber root_blkno = InvalidBlockNumber;
 	OffsetNumber root_offsets[MaxHeapTuplesPerPage];
+	BlockNumber blocks_done = 0;
+	BlockNumber	previous_blkno = InvalidBlockNumber;
 
 	/*
 	 * sanity checks
@@ -2592,6 +2617,45 @@ IndexBuildHeapRangeScan(Relation heapRelation,
 
 		CHECK_FOR_INTERRUPTS();
 
+		if (progress &&
+			((previous_blkno == InvalidBlockNumber) ||
+			(scan->rs_cblock != previous_blkno)))
+		{
+			/* we only do progress for full table scans */
+			Assert(numblocks == InvalidBlockNumber);
+
+			/*
+			 * Report the number of blocks we've moved forward.
+			 *
+			 * Parallel workers cause the leader process to skip some blocks,
+			 * so we subtract the current block number to the previous one to
+			 * determine how many have been read in total; but be careful when
+			 * we wrap around the last block in the table.
+			 */
+			if (scan->rs_cblock > previous_blkno)
+				blocks_done += scan->rs_cblock - previous_blkno;
+			else if (previous_blkno == InvalidBlockNumber)
+			{
+				/*
+				 * How many blocks have been read since the scan started.
+				 * Should normally be zero.
+				 */
+				blocks_done += scan->rs_cblock -
+					(scan->rs_parallel ?  scan->rs_parallel->phs_startblock :
+					 scan->rs_startblock);
+				Assert(blocks_done == 0);
+			}
+			else
+			{
+				/* wrapped around */
+				blocks_done += scan->rs_nblocks - previous_blkno + scan->rs_cblock;
+			}
+
+			pgstat_progress_update_param(PROGRESS_SCAN_BLOCKS_DONE,
+										 blocks_done);
+			previous_blkno = scan->rs_cblock;
+		}
+
 		/*
 		 * When dealing with a HOT-chain of updated tuples, we want to index
 		 * the values of the live tuple (if any), but index it under the TID
@@ -3133,6 +3197,21 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	int			save_sec_context;
 	int			save_nestlevel;
 
+	{
+		const int	index[] = {
+			PROGRESS_CREATEIDX_PHASE,
+			PROGRESS_CREATEIDX_TUPLES_DONE,
+			PROGRESS_CREATEIDX_TUPLES_TOTAL,
+			PROGRESS_SCAN_BLOCKS_DONE,
+			PROGRESS_SCAN_BLOCKS_TOTAL
+		};
+		const int64	val[] = {
+			PROGRESS_CREATEIDX_PHASE_VALIDATE_IDXSCAN,
+			0, 0, 0, 0
+		};
+		pgstat_progress_update_multi_param(5, index, val);
+	}
+
 	/* Open and lock the parent heap relation */
 	heapRelation = table_open(heapId, ShareUpdateExclusiveLock);
 	/* And the target index relation */
@@ -3163,6 +3242,7 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 	 */
 	ivinfo.index = indexRelation;
 	ivinfo.analyze_only = false;
+	ivinfo.report_progress = true;	/* XXX only for btree? */
 	ivinfo.estimated_count = true;
 	ivinfo.message_level = DEBUG2;
 	ivinfo.num_heap_tuples = heapRelation->rd_rel->reltuples;
@@ -3180,15 +3260,31 @@ validate_index(Oid heapId, Oid indexId, Snapshot snapshot)
 											NULL, false);
 	state.htups = state.itups = state.tups_inserted = 0;
 
+	/* ambulkdelete updates progress metrics */
 	(void) index_bulk_delete(&ivinfo, NULL,
 							 validate_index_callback, (void *) &state);
 
 	/* Execute the sort */
+	pgstat_progress_update_param(PROGRESS_CREATEIDX_PHASE,
+								 PROGRESS_CREATEIDX_PHASE_SORT_IDXSCAN);
 	tuplesort_performsort(state.tuplesort);
 
 	/*
-	 * Now scan the heap and "merge" it with the index
+	 * Now scan the heap and "merge" it with the index.
 	 */
+	{
+		const int	index[] = {
+			PROGRESS_CREATEIDX_PHASE,
+			PROGRESS_SCAN_BLOCKS_DONE,
+			PROGRESS_SCAN_BLOCKS_TOTAL
+		};
+		const int64	val[] = {
+			PROGRESS_CREATEIDX_PHASE_HEAPSCAN_VALIDATE,
+			0, 0
+		};
+
+		pgstat_progress_update_multi_param(3, index, val);
+	}
 	validate_index_heapscan(heapRelation,
 							indexRelation,
 							indexInfo,
@@ -3330,6 +3426,9 @@ validate_index_heapscan(Relation heapRelation,
 								true,	/* buffer access strategy OK */
 								false); /* syncscan not OK */
 
+	pgstat_progress_update_param(PROGRESS_CREATEIDX_TUPLES_TOTAL,
+								 state->itups);
+
 	/*
 	 * Scan all tuples matching the snapshot.
 	 */
@@ -3343,6 +3442,9 @@ validate_index_heapscan(Relation heapRelation,
 
 		state->htups += 1;
 
+		pgstat_progress_update_param(PROGRESS_CREATEIDX_TUPLES_DONE,
+									 state->htups);
+
 		/*
 		 * As commented in IndexBuildHeapScan, we should index heap-only
 		 * tuples under the TIDs of their root tuples; so when we advance onto
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 3e229c693c4..22c163be859 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -906,6 +906,32 @@ CREATE VIEW pg_stat_progress_vacuum AS
     FROM pg_stat_get_progress_info('VACUUM') AS S
 		LEFT JOIN pg_database D ON S.datid = D.oid;
 
+CREATE VIEW pg_stat_progress_create_index AS
+	SELECT
+		s.pid AS pid, S.datid AS datid, D.datname AS datname,
+		S.relid AS relid,
+		CASE s.param2 WHEN 0 THEN 'initializing (phase 1 of 8)'
+					  WHEN 1 THEN 'waiting for lockers 1 (phase 2 of 8)'
+					  WHEN 2 THEN 'building index (3 of 8): ' ||
+						pg_indexam_progress_phasename(s.param1::oid, s.param3)
+					  WHEN 3 THEN 'waiting for lockers 2 (phase 4 of 8)'
+					  WHEN 4 THEN 'validating index scan (phase 5 of 8)'
+					  WHEN 5 THEN 'sorting index scan results (phase 6 of 8)'
+					  WHEN 6 THEN 'validate index heapscan (phase 7 of 8)'
+					  WHEN 7 THEN 'waiting for lockers 3 (phase 8 of 8)'
+					  END as phase,
+		S.param4 AS "lockers total",
+		S.param5 AS "lockers done",
+		S.param6 AS "PID of current locker",
+		S.param7 AS "blocks total",
+		S.param8 AS "blocks done",
+		S.param9 AS "tuples total",
+		S.param10 AS "tuples done",
+		S.param11 AS "partitions total",
+		S.param12 AS "partitions done"
+	FROM pg_stat_get_progress_info('CREATE INDEX') AS S
+		LEFT JOIN pg_database D ON S.datid = D.oid;
+
 CREATE VIEW pg_user_mappings AS
     SELECT
         U.oid       AS umid,
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 7352b9e341d..115d7c61359 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -36,6 +36,7 @@
 #include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/event_trigger.h"
+#include "commands/progress.h"
 #include "commands/tablecmds.h"
 #include "commands/tablespace.h"
 #include "mb/pg_wchar.h"
@@ -46,10 +47,12 @@
 #include "parser/parse_coerce.h"
 #include "parser/parse_func.h"
 #include "parser/parse_oper.h"
+#include "pgstat.h"
 #include "rewrite/rewriteManip.h"
 #include "storage/lmgr.h"
 #include "storage/proc.h"
 #include "storage/procarray.h"
+#include "storage/sinvaladt.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
 #include "utils/fmgroids.h"
@@ -368,6 +371,15 @@ DefineIndex(Oid relationId,
 	Snapshot	snapshot;
 	int			i;
 
+
+	/*
+	 * Start progress report.  If we're building a partition, this was already
+	 * done.
+	 */
+	if (!OidIsValid(parentIndexId))
+		pgstat_progress_start_command(PROGRESS_COMMAND_CREATE_INDEX,
+									  relationId);
+
 	/*
 	 * count key attributes in index
 	 */
@@ -584,6 +596,9 @@ DefineIndex(Oid relationId,
 	accessMethodId = accessMethodForm->oid;
 	amRoutine = GetIndexAmRoutine(accessMethodForm->amhandler);
 
+	pgstat_progress_update_param(PROGRESS_CREATEIDX_ACCESS_METHOD_OID,
+								 accessMethodId);
+
 	if (stmt->unique && !amRoutine->amcanunique)
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
@@ -864,6 +879,11 @@ DefineIndex(Oid relationId,
 	if (!OidIsValid(indexRelationId))
 	{
 		table_close(rel, NoLock);
+
+		/* If this is the top-level index, we're done */
+		if (!OidIsValid(parentIndexId))
+			pgstat_progress_end_command();
+
 		return address;
 	}
 
@@ -889,6 +909,9 @@ DefineIndex(Oid relationId,
 			TupleDesc	parentDesc;
 			Oid		   *opfamOids;
 
+			pgstat_progress_update_param(PROGRESS_CREATEIDX_PARTITIONS_TOTAL,
+										 nparts);
+
 			memcpy(part_oids, partdesc->oids, sizeof(Oid) * nparts);
 
 			parentDesc = CreateTupleDescCopy(RelationGetDescr(rel));
@@ -1039,6 +1062,8 @@ DefineIndex(Oid relationId,
 								skip_build, quiet);
 				}
 
+				pgstat_progress_update_param(PROGRESS_CREATEIDX_PARTITIONS_DONE,
+											 i + 1);
 				pfree(attmap);
 			}
 
@@ -1073,6 +1098,8 @@ DefineIndex(Oid relationId,
 		 * Indexes on partitioned tables are not themselves built, so we're
 		 * done here.
 		 */
+		if (!OidIsValid(parentIndexId))
+			pgstat_progress_end_command();
 		return address;
 	}
 
@@ -1080,6 +1107,11 @@ DefineIndex(Oid relationId,
 	{
 		/* Close the heap and we're done, in the non-concurrent case */
 		table_close(rel, NoLock);
+
+		/* If this is the top-level index, we're done. */
+		if (!OidIsValid(parentIndexId))
+			pgstat_progress_end_command();
+
 		return address;
 	}
 
@@ -1131,7 +1163,9 @@ DefineIndex(Oid relationId,
 	 * exclusive lock on our table.  The lock code will detect deadlock and
 	 * error out properly.
 	 */
-	WaitForLockers(heaplocktag, ShareLock);
+	pgstat_progress_update_param(PROGRESS_CREATEIDX_PHASE,
+								 PROGRESS_CREATEIDX_PHASE_WAIT_1);
+	WaitForLockers(heaplocktag, ShareLock, true);
 
 	/*
 	 * At this moment we are sure that there are no transactions with the
@@ -1195,7 +1229,9 @@ DefineIndex(Oid relationId,
 	 * We once again wait until no transaction can have the table open with
 	 * the index marked as read-only for updates.
 	 */
-	WaitForLockers(heaplocktag, ShareLock);
+	pgstat_progress_update_param(PROGRESS_CREATEIDX_PHASE,
+								 PROGRESS_CREATEIDX_PHASE_WAIT_2);
+	WaitForLockers(heaplocktag, ShareLock, true);
 
 	/*
 	 * Now take the "reference snapshot" that will be used by validate_index()
@@ -1281,6 +1317,9 @@ DefineIndex(Oid relationId,
 	old_snapshots = GetCurrentVirtualXIDs(limitXmin, true, false,
 										  PROC_IS_AUTOVACUUM | PROC_IN_VACUUM,
 										  &n_old_snapshots);
+	pgstat_progress_update_param(PROGRESS_CREATEIDX_PHASE,
+								 PROGRESS_CREATEIDX_PHASE_WAIT_3);
+	pgstat_progress_update_param(PROGRESS_WAITFOR_TOTAL, n_old_snapshots);
 
 	for (i = 0; i < n_old_snapshots; i++)
 	{
@@ -1316,7 +1355,14 @@ DefineIndex(Oid relationId,
 		}
 
 		if (VirtualTransactionIdIsValid(old_snapshots[i]))
+		{
+			PGPROC *holder = BackendIdGetProc(old_snapshots[i].backendId);
+			pgstat_progress_update_param(PROGRESS_WAITFOR_CURRENT_PID,
+										 holder->pid);
 			VirtualXactLock(old_snapshots[i], true);
+		}
+
+		pgstat_progress_update_param(PROGRESS_WAITFOR_DONE, i + 1);
 	}
 
 	/*
@@ -1339,6 +1385,8 @@ DefineIndex(Oid relationId,
 	 */
 	UnlockRelationIdForSession(&heaprelid, ShareUpdateExclusiveLock);
 
+	pgstat_progress_end_command();
+
 	return address;
 }
 
diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c
index 4d10e57a803..a0a2b964703 100644
--- a/src/backend/storage/ipc/standby.c
+++ b/src/backend/storage/ipc/standby.c
@@ -401,7 +401,7 @@ ResolveRecoveryConflictWithLock(LOCKTAG locktag)
 		 */
 		VirtualTransactionId *backends;
 
-		backends = GetLockConflicts(&locktag, AccessExclusiveLock);
+		backends = GetLockConflicts(&locktag, AccessExclusiveLock, NULL);
 		ResolveRecoveryConflictWithVirtualXIDs(backends,
 											   PROCSIG_RECOVERY_CONFLICT_LOCK);
 	}
diff --git a/src/backend/storage/lmgr/lmgr.c b/src/backend/storage/lmgr/lmgr.c
index e688ba81170..0b04b093782 100644
--- a/src/backend/storage/lmgr/lmgr.c
+++ b/src/backend/storage/lmgr/lmgr.c
@@ -19,9 +19,12 @@
 #include "access/transam.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "commands/progress.h"
 #include "miscadmin.h"
+#include "pgstat.h"
 #include "storage/lmgr.h"
 #include "storage/procarray.h"
+#include "storage/sinvaladt.h"
 #include "utils/inval.h"
 
 
@@ -857,10 +860,12 @@ XactLockTableWaitErrorCb(void *arg)
  * after we obtained our initial list of lockers, we will not wait for them.
  */
 void
-WaitForLockersMultiple(List *locktags, LOCKMODE lockmode)
+WaitForLockersMultiple(List *locktags, LOCKMODE lockmode, bool progress)
 {
 	List	   *holders = NIL;
 	ListCell   *lc;
+	int			total = 0;
+	int			done = 0;
 
 	/* Done if no locks to wait for */
 	if (list_length(locktags) == 0)
@@ -870,10 +875,17 @@ WaitForLockersMultiple(List *locktags, LOCKMODE lockmode)
 	foreach(lc, locktags)
 	{
 		LOCKTAG    *locktag = lfirst(lc);
+		int			count;
 
-		holders = lappend(holders, GetLockConflicts(locktag, lockmode));
+		holders = lappend(holders,
+						  GetLockConflicts(locktag, lockmode,
+										   progress ? &count : NULL));
+		total += count;
 	}
 
+	if (progress)
+		pgstat_progress_update_param(PROGRESS_WAITFOR_TOTAL, total);
+
 	/*
 	 * Note: GetLockConflicts() never reports our own xid, hence we need not
 	 * check for that.  Also, prepared xacts are not reported, which is fine
@@ -887,10 +899,36 @@ WaitForLockersMultiple(List *locktags, LOCKMODE lockmode)
 
 		while (VirtualTransactionIdIsValid(*lockholders))
 		{
+			/*
+			 * If requested, publish who we're going to wait for.  This is not
+			 * 100% accurate if they're already gone, but we don't care.
+			 */
+			if (progress)
+			{
+				PGPROC *holder = BackendIdGetProc(lockholders->backendId);
+
+				pgstat_progress_update_param(PROGRESS_WAITFOR_CURRENT_PID,
+											 holder->pid);
+			}
 			VirtualXactLock(*lockholders, true);
 			lockholders++;
+
+			if (progress)
+				pgstat_progress_update_param(PROGRESS_WAITFOR_DONE, ++done);
 		}
 	}
+	if (progress)
+	{
+		const int	index[] = {
+			PROGRESS_WAITFOR_TOTAL,
+			PROGRESS_WAITFOR_DONE,
+			PROGRESS_WAITFOR_CURRENT_PID
+		};
+		const int64	values[] = {
+			0, 0, 0
+		};
+		pgstat_progress_update_multi_param(3, index, values);
+	}
 
 	list_free_deep(holders);
 }
@@ -901,12 +939,12 @@ WaitForLockersMultiple(List *locktags, LOCKMODE lockmode)
  * Same as WaitForLockersMultiple, for a single lock tag.
  */
 void
-WaitForLockers(LOCKTAG heaplocktag, LOCKMODE lockmode)
+WaitForLockers(LOCKTAG heaplocktag, LOCKMODE lockmode, bool progress)
 {
 	List	   *l;
 
 	l = list_make1(&heaplocktag);
-	WaitForLockersMultiple(l, lockmode);
+	WaitForLockersMultiple(l, lockmode, progress);
 	list_free(l);
 }
 
diff --git a/src/backend/storage/lmgr/lock.c b/src/backend/storage/lmgr/lock.c
index 3bb5ce350aa..58ba90d0646 100644
--- a/src/backend/storage/lmgr/lock.c
+++ b/src/backend/storage/lmgr/lock.c
@@ -2807,6 +2807,7 @@ FastPathGetRelationLockEntry(LOCALLOCK *locallock)
  *		xacts merely awaiting such a lock are NOT reported.
  *
  * The result array is palloc'd and is terminated with an invalid VXID.
+ * *countp, if not null, is updated to the number of items set.
  *
  * Of course, the result could be out of date by the time it's returned,
  * so use of this function has to be thought about carefully.
@@ -2817,7 +2818,7 @@ FastPathGetRelationLockEntry(LOCALLOCK *locallock)
  * uses of the result.
  */
 VirtualTransactionId *
-GetLockConflicts(const LOCKTAG *locktag, LOCKMODE lockmode)
+GetLockConflicts(const LOCKTAG *locktag, LOCKMODE lockmode, int *countp)
 {
 	static VirtualTransactionId *vxids;
 	LOCKMETHODID lockmethodid = locktag->locktag_lockmethodid;
@@ -2964,6 +2965,8 @@ GetLockConflicts(const LOCKTAG *locktag, LOCKMODE lockmode)
 		LWLockRelease(partitionLock);
 		vxids[count].backendId = InvalidBackendId;
 		vxids[count].localTransactionId = InvalidLocalTransactionId;
+		if (countp)
+			*countp = count;
 		return vxids;
 	}
 
@@ -3019,6 +3022,8 @@ GetLockConflicts(const LOCKTAG *locktag, LOCKMODE lockmode)
 
 	vxids[count].backendId = InvalidBackendId;
 	vxids[count].localTransactionId = InvalidLocalTransactionId;
+	if (countp)
+		*countp = count;
 	return vxids;
 }
 
diff --git a/src/backend/utils/adt/amutils.c b/src/backend/utils/adt/amutils.c
index 060ffe501ec..05046e9b559 100644
--- a/src/backend/utils/adt/amutils.c
+++ b/src/backend/utils/adt/amutils.c
@@ -445,3 +445,26 @@ pg_index_column_has_property(PG_FUNCTION_ARGS)
 
 	return indexam_property(fcinfo, propname, InvalidOid, relid, attno);
 }
+
+/*
+ * Return the name of the given phase, as used for progress reporting by the
+ * given AM.
+ */
+Datum
+pg_indexam_progress_phasename(PG_FUNCTION_ARGS)
+{
+	Oid			amoid = PG_GETARG_OID(0);
+	int32		phasenum = PG_GETARG_INT32(1);
+	IndexAmRoutine *routine;
+	char	   *name;
+
+	routine = GetIndexAmRoutineByAmId(amoid, true);
+	if (routine == NULL || !routine->amphasename)
+		PG_RETURN_NULL();
+
+	name = routine->amphasename(phasenum);
+	if (!name)
+		PG_RETURN_NULL();
+
+	PG_RETURN_TEXT_P(CStringGetTextDatum(name));
+}
diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c
index b6ba856ebe6..91dc5fed9cd 100644
--- a/src/backend/utils/adt/pgstatfuncs.c
+++ b/src/backend/utils/adt/pgstatfuncs.c
@@ -468,6 +468,8 @@ pg_stat_get_progress_info(PG_FUNCTION_ARGS)
 	/* Translate command name into command type code. */
 	if (pg_strcasecmp(cmd, "VACUUM") == 0)
 		cmdtype = PROGRESS_COMMAND_VACUUM;
+	else if (pg_strcasecmp(cmd, "CREATE INDEX") == 0)
+		cmdtype = PROGRESS_COMMAND_CREATE_INDEX;
 	else
 		ereport(ERROR,
 				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
diff --git a/src/include/access/amapi.h b/src/include/access/amapi.h
index 653ddc976ba..471625db14f 100644
--- a/src/include/access/amapi.h
+++ b/src/include/access/amapi.h
@@ -108,6 +108,9 @@ typedef bool (*amproperty_function) (Oid index_oid, int attno,
 									 IndexAMProperty prop, const char *propname,
 									 bool *res, bool *isnull);
 
+/* name of phase as used in progress reporting */
+typedef char *(*amphasename_function) (int64 phasenum);
+
 /* validate definition of an opclass for this AM */
 typedef bool (*amvalidate_function) (Oid opclassoid);
 
@@ -213,6 +216,7 @@ typedef struct IndexAmRoutine
 	amcostestimate_function amcostestimate;
 	amoptions_function amoptions;
 	amproperty_function amproperty; /* can be NULL */
+	amphasename_function amphasename;	/* can be NULL */
 	amvalidate_function amvalidate;
 	ambeginscan_function ambeginscan;
 	amrescan_function amrescan;
diff --git a/src/include/access/genam.h b/src/include/access/genam.h
index c4aba39496f..f77d9eea8e8 100644
--- a/src/include/access/genam.h
+++ b/src/include/access/genam.h
@@ -45,6 +45,7 @@ typedef struct IndexVacuumInfo
 {
 	Relation	index;			/* the index being vacuumed */
 	bool		analyze_only;	/* ANALYZE (without any actual vacuum) */
+	bool		report_progress;	/* emit progress.h status reports */
 	bool		estimated_count;	/* num_heap_tuples is an estimate */
 	int			message_level;	/* ereport level for progress messages */
 	double		num_heap_tuples;	/* tuples remaining in heap */
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index 4fb92d60a12..b53b9016870 100644
--- a/src/include/access/nbtree.h
+++ b/src/include/access/nbtree.h
@@ -488,6 +488,16 @@ typedef BTScanOpaqueData *BTScanOpaque;
 #define SK_BT_DESC			(INDOPTION_DESC << SK_BT_INDOPTION_SHIFT)
 #define SK_BT_NULLS_FIRST	(INDOPTION_NULLS_FIRST << SK_BT_INDOPTION_SHIFT)
 
+/*
+ * Constant definition for progress reporting.  Phase numbers must match
+ * btphasename.
+ */
+/* PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE is 1 (see progress.h) */
+#define PROGRESS_BTREE_PHASE_INDEXBUILD_HEAPSCAN		2
+#define PROGRESS_BTREE_PHASE_PERFORMSORT_1				3
+#define PROGRESS_BTREE_PHASE_PERFORMSORT_2				4
+#define PROGRESS_BTREE_PHASE_LEAF_LOAD					5
+
 /*
  * external entry points for btree, in nbtree.c
  */
@@ -600,6 +610,7 @@ extern bytea *btoptions(Datum reloptions, bool validate);
 extern bool btproperty(Oid index_oid, int attno,
 		   IndexAMProperty prop, const char *propname,
 		   bool *res, bool *isnull);
+extern char *btphasename(int64 phasenum);
 extern IndexTuple _bt_nonkey_truncate(Relation rel, IndexTuple itup);
 extern bool _bt_check_natts(Relation rel, Page page, OffsetNumber offnum);
 
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index 330c481a8b7..fb713d50f96 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -114,6 +114,7 @@ extern double IndexBuildHeapScan(Relation heapRelation,
 				   Relation indexRelation,
 				   IndexInfo *indexInfo,
 				   bool allow_sync,
+				   bool progress,
 				   IndexBuildCallback callback,
 				   void *callback_state,
 				   struct HeapScanDescData *scan);
@@ -122,6 +123,7 @@ extern double IndexBuildHeapRangeScan(Relation heapRelation,
 						IndexInfo *indexInfo,
 						bool allow_sync,
 						bool anyvisible,
+						bool progress,
 						BlockNumber start_blockno,
 						BlockNumber end_blockno,
 						IndexBuildCallback callback,
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index 24f99f7fc45..e523c013a11 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -917,6 +917,10 @@
   proname => 'pg_index_column_has_property', provolatile => 's',
   prorettype => 'bool', proargtypes => 'regclass int4 text',
   prosrc => 'pg_index_column_has_property' },
+{ oid => '676', descr => 'return name of given index build phase',
+  proname => 'pg_indexam_progress_phasename', provolatile => 'i',
+  prorettype => 'text', proargtypes => 'oid int8',
+  prosrc => 'pg_indexam_progress_phasename' },
 
 { oid => '339',
   proname => 'poly_same', prorettype => 'bool',
@@ -5079,9 +5083,9 @@
   proname => 'pg_stat_get_progress_info', prorows => '100', proretset => 't',
   provolatile => 's', proparallel => 'r', prorettype => 'record',
   proargtypes => 'text',
-  proallargtypes => '{text,int4,oid,oid,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8}',
-  proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o}',
-  proargnames => '{cmdtype,pid,datid,relid,param1,param2,param3,param4,param5,param6,param7,param8,param9,param10}',
+  proallargtypes => '{text,int4,oid,oid,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8,int8}',
+  proargmodes => '{i,o,o,o,o,o,o,o,o,o,o,o,o,o,o,o}',
+  proargnames => '{cmdtype,pid,datid,relid,param1,param2,param3,param4,param5,param6,param7,param8,param9,param10,param11,param12}',
   prosrc => 'pg_stat_get_progress_info' },
 { oid => '3099',
   descr => 'statistics: information about currently active replication',
diff --git a/src/include/commands/progress.h b/src/include/commands/progress.h
index 9858b36a383..4dae1e7438d 100644
--- a/src/include/commands/progress.h
+++ b/src/include/commands/progress.h
@@ -34,4 +34,40 @@
 #define PROGRESS_VACUUM_PHASE_TRUNCATE			5
 #define PROGRESS_VACUUM_PHASE_FINAL_CLEANUP		6
 
+
+/* Progress parameters for CREATE INDEX */
+#define PROGRESS_CREATEIDX_ACCESS_METHOD_OID	0
+#define PROGRESS_CREATEIDX_PHASE				1
+#define PROGRESS_CREATEIDX_SUBPHASE				2
+/* 3, 4 and 5 reserved for "waitfor" metrics */
+/* 6 and 7 reserved for "block number" metrics */
+#define PROGRESS_CREATEIDX_TUPLES_TOTAL			8
+#define PROGRESS_CREATEIDX_TUPLES_DONE			9
+#define PROGRESS_CREATEIDX_PARTITIONS_TOTAL		10
+#define PROGRESS_CREATEIDX_PARTITIONS_DONE		11
+
+/* Phases of CREATE INDEX */
+#define PROGRESS_CREATEIDX_PHASE_WAIT_1			1
+#define PROGRESS_CREATEIDX_PHASE_BUILD			2
+#define PROGRESS_CREATEIDX_PHASE_WAIT_2			3
+#define PROGRESS_CREATEIDX_PHASE_VALIDATE_IDXSCAN		4
+#define PROGRESS_CREATEIDX_PHASE_SORT_IDXSCAN		5
+#define PROGRESS_CREATEIDX_PHASE_HEAPSCAN_VALIDATE		6
+#define PROGRESS_CREATEIDX_PHASE_WAIT_3			7
+
+/*
+ * Subphases of CREATE INDEX, for index_build.
+ */
+#define PROGRESS_CREATEIDX_SUBPHASE_INITIALIZE	1
+/* Additional phases are defined by each AM */
+
+/* Lock holder wait counts */
+#define PROGRESS_WAITFOR_TOTAL					3
+#define PROGRESS_WAITFOR_DONE					4
+#define PROGRESS_WAITFOR_CURRENT_PID			5
+
+/* Block numbers in a generic relation scan */
+#define PROGRESS_SCAN_BLOCKS_TOTAL				6
+#define PROGRESS_SCAN_BLOCKS_DONE				7
+
 #endif
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 88a75fb798e..f931ead1c27 100644
--- a/src/include/pgstat.h
+++ b/src/include/pgstat.h
@@ -934,10 +934,11 @@ typedef enum
 typedef enum ProgressCommandType
 {
 	PROGRESS_COMMAND_INVALID,
-	PROGRESS_COMMAND_VACUUM
+	PROGRESS_COMMAND_VACUUM,
+	PROGRESS_COMMAND_CREATE_INDEX
 } ProgressCommandType;
 
-#define PGSTAT_NUM_PROGRESS_PARAM	10
+#define PGSTAT_NUM_PROGRESS_PARAM	12
 
 /* ----------
  * Shared-memory data structures
diff --git a/src/include/storage/lmgr.h b/src/include/storage/lmgr.h
index 3d705faba5c..4f2872de35f 100644
--- a/src/include/storage/lmgr.h
+++ b/src/include/storage/lmgr.h
@@ -78,8 +78,8 @@ extern void XactLockTableWait(TransactionId xid, Relation rel,
 extern bool ConditionalXactLockTableWait(TransactionId xid);
 
 /* Lock VXIDs, specified by conflicting locktags */
-extern void WaitForLockers(LOCKTAG heaplocktag, LOCKMODE lockmode);
-extern void WaitForLockersMultiple(List *locktags, LOCKMODE lockmode);
+extern void WaitForLockers(LOCKTAG heaplocktag, LOCKMODE lockmode, bool progress);
+extern void WaitForLockersMultiple(List *locktags, LOCKMODE lockmode, bool progress);
 
 /* Lock an XID for tuple insertion (used to wait for an insertion to finish) */
 extern uint32 SpeculativeInsertionLockAcquire(TransactionId xid);
diff --git a/src/include/storage/lock.h b/src/include/storage/lock.h
index 16b927cb801..e117b391774 100644
--- a/src/include/storage/lock.h
+++ b/src/include/storage/lock.h
@@ -544,7 +544,7 @@ extern bool LockHeldByMe(const LOCKTAG *locktag, LOCKMODE lockmode);
 extern bool LockHasWaiters(const LOCKTAG *locktag,
 			   LOCKMODE lockmode, bool sessionLock);
 extern VirtualTransactionId *GetLockConflicts(const LOCKTAG *locktag,
-				 LOCKMODE lockmode);
+				 LOCKMODE lockmode, int *countp);
 extern void AtPrepare_Locks(void);
 extern void PostPrepare_Locks(TransactionId xid);
 extern int LockCheckConflicts(LockMethod lockMethodTable,

Reply via email to