pg_stats_recovery view

Started by Jaime Casanovaalmost 14 years ago13 messages
#1Jaime Casanova
jaime@2ndquadrant.com
1 attachment(s)

Hi,

Attached is a patch thats implements a pg_stat_recovery view that
keeps counters about processed wal records. I just notice that it
still lacks documentation but i will add it during the week.

Because it tracks redo time this introduces to GetCurrentTimestamp()
calls to the redo main loop, so i add a track_recovery GUC so only the
people that wants the view has to spent time in those calls.

Probably the most controversial part of the patch will be the addition
of a new column in RmgrData that is a pointer to a new function for
*_short_desc() these functions are similar to the *_desc functions
that already exists and that is called via rm_desc but instead of
giving full details about the record being processed it just inform of
the type of the record, for example the 2 first columns will look
something like:

rmgr: XLOG
wal_record_type: xlog switch

--
Jaime Casanova         www.2ndQuadrant.com
Professional PostgreSQL: Soporte 24x7 y capacitación

Attachments:

stats_recovery.v1.patchtext/x-patch; charset=US-ASCII; name=stats_recovery.v1.patchDownload
diff --git a/src/backend/access/gin/ginxlog.c b/src/backend/access/gin/ginxlog.c
new file mode 100644
index 94051aa..9891ebb
*** a/src/backend/access/gin/ginxlog.c
--- b/src/backend/access/gin/ginxlog.c
*************** gin_desc(StringInfo buf, uint8 xl_info,
*** 779,784 ****
--- 779,823 ----
  }
  
  void
+ gin_short_desc(char buf[50], uint8 xl_info)
+ {
+ 	uint8		info = xl_info & ~XLR_INFO_MASK;
+ 
+ 	switch (info)
+ 	{
+ 		case XLOG_GIN_CREATE_INDEX:
+ 			sprintf(buf, "Create index");
+ 			break;
+ 		case XLOG_GIN_CREATE_PTREE:
+ 			sprintf(buf, "Create posting tree");
+ 			break;
+ 		case XLOG_GIN_INSERT:
+ 			sprintf(buf, "Insert item");
+ 			break;
+ 		case XLOG_GIN_SPLIT:
+ 			sprintf(buf, "Page split");
+ 			break;
+ 		case XLOG_GIN_VACUUM_PAGE:
+ 			sprintf(buf, "Vacuum page");
+ 			break;
+ 		case XLOG_GIN_DELETE_PAGE:
+ 			sprintf(buf, "Delete page");
+ 			break;
+ 		case XLOG_GIN_UPDATE_META_PAGE:
+ 			sprintf(buf, "Update metapage");
+ 			break;
+ 		case XLOG_GIN_INSERT_LISTPAGE:
+ 			sprintf(buf, "Insert new list page");
+ 			break;
+ 		case XLOG_GIN_DELETE_LISTPAGE:
+ 			sprintf(buf, "Delete list pages");
+ 			break;
+ 		default:
+ 			sprintf(buf, "UNKNOWN GIN INFO");
+ 	}
+ }
+ 
+ void
  gin_xlog_startup(void)
  {
  	incomplete_splits = NIL;
diff --git a/src/backend/access/gist/gistxlog.c b/src/backend/access/gist/gistxlog.c
new file mode 100644
index 76029d9..397afaf
*** a/src/backend/access/gist/gistxlog.c
--- b/src/backend/access/gist/gistxlog.c
*************** gist_desc(StringInfo buf, uint8 xl_info,
*** 394,399 ****
--- 394,424 ----
  }
  
  void
+ gist_short_desc(char buf[50], uint8 xl_info)
+ {
+ 	uint8		info = xl_info & ~XLR_INFO_MASK;
+ 
+ 	switch (info)
+ 	{
+ 		case XLOG_GIST_PAGE_UPDATE:
+ 			sprintf(buf, "page_update");
+ 			break;
+ 		case XLOG_GIST_PAGE_DELETE:
+ 			sprintf(buf, "page_delete");
+ 			break;
+ 		case XLOG_GIST_PAGE_SPLIT:
+ 			sprintf(buf, "page_split");
+ 			break;
+ 		case XLOG_GIST_CREATE_INDEX:
+ 			sprintf(buf, "create_index");
+ 			break;
+ 		default:
+ 			sprintf(buf, "UNKNOWN GIST INFO");
+ 			break;
+ 	}
+ }
+ 
+ void
  gist_xlog_startup(void)
  {
  	opCtx = createTempGistContext();
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
new file mode 100644
index 8802669..f82a20a
*** a/src/backend/access/hash/hash.c
--- b/src/backend/access/hash/hash.c
*************** void
*** 717,719 ****
--- 717,724 ----
  hash_desc(StringInfo buf, uint8 xl_info, char *rec)
  {
  }
+ 
+ void
+ hash_short_desc(char buf[50], uint8 xl_info)
+ {
+ }
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
new file mode 100644
index 5f6ac2e..bad5e0c
*** a/src/backend/access/heap/heapam.c
--- b/src/backend/access/heap/heapam.c
*************** heap2_desc(StringInfo buf, uint8 xl_info
*** 5709,5714 ****
--- 5709,5788 ----
  		appendStringInfo(buf, "UNKNOWN");
  }
  
+ void
+ heap_short_desc(char buf[50], uint8 xl_info)
+ {
+ 	uint8		info = xl_info & ~XLR_INFO_MASK;
+ 
+ 	info &= XLOG_HEAP_OPMASK;
+ 	switch (info)
+ 	{
+ 		case XLOG_HEAP_INSERT:
+ 			if (xl_info & XLOG_HEAP_INIT_PAGE)
+ 				sprintf(buf, "insert(init)");
+ 			else
+ 				sprintf(buf, "insert");
+ 			break;
+ 		case XLOG_HEAP_DELETE:
+ 			sprintf(buf, "delete");
+ 			break;
+ 		case XLOG_HEAP_UPDATE:
+ 			if (xl_info & XLOG_HEAP_INIT_PAGE)
+ 				sprintf(buf, "update(init)");
+ 			else
+ 				sprintf(buf, "update");
+ 			break;
+ 		case XLOG_HEAP_HOT_UPDATE:
+ 			if (xl_info & XLOG_HEAP_INIT_PAGE)		/* can this case happen? */
+ 				sprintf(buf, "hot_update(init)");
+ 			else
+ 				sprintf(buf, "hot_update");
+ 			break;
+ 		case XLOG_HEAP_NEWPAGE:
+ 			sprintf(buf, "newpage");
+ 			break;
+ 		case XLOG_HEAP_LOCK:
+ 			sprintf(buf, "lock");
+ 			break;
+ 		case XLOG_HEAP_INPLACE:
+ 			sprintf(buf, "inplace");
+ 			break;
+ 		default:
+ 			sprintf(buf, "UNKNOWN HEAP INFO");
+ 	}
+ }
+ 
+ void
+ heap2_short_desc(char buf[50], uint8 xl_info)
+ {
+ 	uint8		info = xl_info & ~XLR_INFO_MASK;
+ 
+ 	info &= XLOG_HEAP_OPMASK;
+ 	switch (info)
+ 	{
+ 		case XLOG_HEAP2_FREEZE:
+ 			sprintf(buf, "freeze");
+ 			break;
+ 		case XLOG_HEAP2_CLEAN:
+ 			sprintf(buf, "clean");
+ 			break;
+ 		case XLOG_HEAP2_CLEANUP_INFO:
+ 			sprintf(buf, "cleanup info");
+ 			break;	
+ 		case XLOG_HEAP2_VISIBLE:
+ 			sprintf(buf, "visible");
+ 			break;
+ 		case XLOG_HEAP2_MULTI_INSERT:
+ 			if (xl_info & XLOG_HEAP_INIT_PAGE)
+ 				sprintf(buf, "multi-insert (init)");
+ 			else
+ 				sprintf(buf, "multi-insert");
+ 			break;
+ 		default:
+ 			sprintf(buf, "UNKNOWN HEAP2 INFO");
+ 	}
+ }
+ 
  /*
   *	heap_sync		- sync a heap, for use when no WAL has been written
   *
diff --git a/src/backend/access/nbtree/nbtxlog.c b/src/backend/access/nbtree/nbtxlog.c
new file mode 100644
index 0f5c113..4b40e86
*** a/src/backend/access/nbtree/nbtxlog.c
--- b/src/backend/access/nbtree/nbtxlog.c
*************** btree_desc(StringInfo buf, uint8 xl_info
*** 1179,1184 ****
--- 1179,1267 ----
  }
  
  void
+ btree_short_desc(char buf[50], uint8 xl_info)
+ {
+ 	uint8		info = xl_info & ~XLR_INFO_MASK;
+ 
+ 	switch (info)
+ 	{
+ 		case XLOG_BTREE_INSERT_LEAF:
+ 			{
+ 				sprintf(buf, "insert");
+ 				break;
+ 			}
+ 		case XLOG_BTREE_INSERT_UPPER:
+ 			{
+ 				sprintf(buf, "insert_upper");
+ 				break;
+ 			}
+ 		case XLOG_BTREE_INSERT_META:
+ 			{
+ 				sprintf(buf, "insert_meta");
+ 				break;
+ 			}
+ 		case XLOG_BTREE_SPLIT_L:
+ 			{
+ 				sprintf(buf, "split_l");
+ 				break;
+ 			}
+ 		case XLOG_BTREE_SPLIT_R:
+ 			{
+ 				sprintf(buf, "split_r");
+ 				break;
+ 			}
+ 		case XLOG_BTREE_SPLIT_L_ROOT:
+ 			{
+ 				sprintf(buf, "split_l_root");
+ 				break;
+ 			}
+ 		case XLOG_BTREE_SPLIT_R_ROOT:
+ 			{
+ 				sprintf(buf, "split_r_root");
+ 				break;
+ 			}
+ 		case XLOG_BTREE_VACUUM:
+ 			{
+ 				sprintf(buf, "vacuum");
+ 				break;
+ 			}
+ 		case XLOG_BTREE_DELETE:
+ 			{
+ 				sprintf(buf, "delete");
+ 				break;
+ 			}
+ 		case XLOG_BTREE_DELETE_PAGE:
+ 			{
+ 				sprintf(buf, "delete_page");
+ 				break;
+ 			}
+ 		case XLOG_BTREE_DELETE_PAGE_META:
+ 			{
+ 				sprintf(buf, "delete_page_meta");
+ 				break;
+ 			}
+ 		case XLOG_BTREE_DELETE_PAGE_HALF:
+ 			{
+ 				sprintf(buf, "delete_page_half");
+ 				break;
+ 			}
+ 		case XLOG_BTREE_NEWROOT:
+ 			{
+ 				sprintf(buf, "newroot");
+ 				break;
+ 			}
+ 		case XLOG_BTREE_REUSE_PAGE:
+ 			{
+ 				sprintf(buf, "reuse_page");
+ 				break;
+ 			}
+ 		default:
+ 			sprintf(buf, "UNKNOWN BTREE INFO");
+ 			break;
+ 	}
+ }
+ 
+ void
  btree_xlog_startup(void)
  {
  	incomplete_actions = NIL;
diff --git a/src/backend/access/spgist/spgxlog.c b/src/backend/access/spgist/spgxlog.c
new file mode 100644
index daa8ae3..fb0160d
*** a/src/backend/access/spgist/spgxlog.c
--- b/src/backend/access/spgist/spgxlog.c
*************** spg_desc(StringInfo buf, uint8 xl_info,
*** 1053,1058 ****
--- 1053,1098 ----
  }
  
  void
+ spg_short_desc(char buf[50], uint8 xl_info)
+ {
+ 	uint8		info = xl_info & ~XLR_INFO_MASK;
+ 
+ 	switch (info)
+ 	{
+ 		case XLOG_SPGIST_CREATE_INDEX:
+ 			sprintf(buf, "create_index");
+ 			break;
+ 		case XLOG_SPGIST_ADD_LEAF:
+ 			sprintf(buf, "add leaf");
+ 			break;
+ 		case XLOG_SPGIST_MOVE_LEAFS:
+ 			sprintf(buf, "move leafs");
+ 			break;
+ 		case XLOG_SPGIST_ADD_NODE:
+ 			sprintf(buf, "add node");
+ 			break;
+ 		case XLOG_SPGIST_SPLIT_TUPLE:
+ 			sprintf(buf, "split node");
+ 			break;
+ 		case XLOG_SPGIST_PICKSPLIT:
+ 			sprintf(buf, "split leaf page");
+ 			break;
+ 		case XLOG_SPGIST_VACUUM_LEAF:
+ 			sprintf(buf, "vacuum leaf tuples on page");
+ 			break;
+ 		case XLOG_SPGIST_VACUUM_ROOT:
+ 			sprintf(buf, "vacuum leaf tuples on root page");
+ 			break;
+ 		case XLOG_SPGIST_VACUUM_REDIRECT:
+ 			sprintf(buf, "vacuum redirect tuples on page");
+ 			break;
+ 		default:
+ 			sprintf(buf, "UNKNOWN SPGIST INFO");
+ 			break;
+ 	}
+ }
+ 
+ void
  spg_xlog_startup(void)
  {
  	opCtx = AllocSetContextCreate(CurrentMemoryContext,
diff --git a/src/backend/access/transam/clog.c b/src/backend/access/transam/clog.c
new file mode 100644
index 69b6ef3..c2366ee
*** a/src/backend/access/transam/clog.c
--- b/src/backend/access/transam/clog.c
*************** clog_desc(StringInfo buf, uint8 xl_info,
*** 790,792 ****
--- 790,810 ----
  	else
  		appendStringInfo(buf, "UNKNOWN");
  }
+ 
+ void
+ clog_short_desc(char buf[50], uint8 xl_info)
+ {
+ 	uint8		info = xl_info & ~XLR_INFO_MASK;
+ 
+ 	switch (info)
+ 	{
+ 		case CLOG_ZEROPAGE:
+ 			sprintf(buf, "zeropage");
+ 			break;
+ 		case CLOG_TRUNCATE:
+ 			sprintf(buf, "truncate before");
+ 			break;
+ 		default:
+ 			sprintf(buf, "UNKNOWN CLOG INFO");
+ 	}
+ }
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
new file mode 100644
index 454ca31..53b6552
*** a/src/backend/access/transam/multixact.c
--- b/src/backend/access/transam/multixact.c
*************** multixact_desc(StringInfo buf, uint8 xl_
*** 2080,2082 ****
--- 2080,2103 ----
  	else
  		appendStringInfo(buf, "UNKNOWN");
  }
+ 
+ void
+ multixact_short_desc(char buf[50], uint8 xl_info)
+ {
+ 	uint8		info = xl_info & ~XLR_INFO_MASK;
+ 
+ 	switch (info)
+ 	{
+ 		case XLOG_MULTIXACT_ZERO_OFF_PAGE:
+ 			sprintf(buf, "zero offsets page");
+ 			break;
+ 		case XLOG_MULTIXACT_ZERO_MEM_PAGE:
+ 			sprintf(buf, "zero members page");
+ 			break;
+ 		case XLOG_MULTIXACT_CREATE_ID:
+ 			sprintf(buf, "create multixact");
+ 			break;
+ 		default:
+ 			sprintf(buf, "UNKNOWN MULTIXACT INFO");
+ 	}
+ }
diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c
new file mode 100644
index ed8754e..1c27898
*** a/src/backend/access/transam/rmgr.c
--- b/src/backend/access/transam/rmgr.c
***************
*** 26,46 ****
  
  
  const RmgrData RmgrTable[RM_MAX_ID + 1] = {
! 	{"XLOG", xlog_redo, xlog_desc, NULL, NULL, NULL},
! 	{"Transaction", xact_redo, xact_desc, NULL, NULL, NULL},
! 	{"Storage", smgr_redo, smgr_desc, NULL, NULL, NULL},
! 	{"CLOG", clog_redo, clog_desc, NULL, NULL, NULL},
! 	{"Database", dbase_redo, dbase_desc, NULL, NULL, NULL},
! 	{"Tablespace", tblspc_redo, tblspc_desc, NULL, NULL, NULL},
! 	{"MultiXact", multixact_redo, multixact_desc, NULL, NULL, NULL},
! 	{"RelMap", relmap_redo, relmap_desc, NULL, NULL, NULL},
! 	{"Standby", standby_redo, standby_desc, NULL, NULL, NULL},
! 	{"Heap2", heap2_redo, heap2_desc, NULL, NULL, NULL},
! 	{"Heap", heap_redo, heap_desc, NULL, NULL, NULL},
! 	{"Btree", btree_redo, btree_desc, btree_xlog_startup, btree_xlog_cleanup, btree_safe_restartpoint},
! 	{"Hash", hash_redo, hash_desc, NULL, NULL, NULL},
! 	{"Gin", gin_redo, gin_desc, gin_xlog_startup, gin_xlog_cleanup, gin_safe_restartpoint},
! 	{"Gist", gist_redo, gist_desc, gist_xlog_startup, gist_xlog_cleanup, NULL},
! 	{"Sequence", seq_redo, seq_desc, NULL, NULL, NULL},
! 	{"SPGist", spg_redo, spg_desc, spg_xlog_startup, spg_xlog_cleanup, NULL}
  };
--- 26,46 ----
  
  
  const RmgrData RmgrTable[RM_MAX_ID + 1] = {
! 	{"XLOG", xlog_redo, xlog_desc, xlog_short_desc, NULL, NULL, NULL},
! 	{"Transaction", xact_redo, xact_desc, xact_short_desc, NULL, NULL, NULL},
! 	{"Storage", smgr_redo, smgr_desc, smgr_short_desc, NULL, NULL, NULL},
! 	{"CLOG", clog_redo, clog_desc, clog_short_desc, NULL, NULL, NULL},
! 	{"Database", dbase_redo, dbase_desc, dbase_short_desc, NULL, NULL, NULL},
! 	{"Tablespace", tblspc_redo, tblspc_desc, tblspc_short_desc, NULL, NULL, NULL},
! 	{"MultiXact", multixact_redo, multixact_desc, multixact_short_desc, NULL, NULL, NULL},
! 	{"RelMap", relmap_redo, relmap_desc, relmap_short_desc, NULL, NULL, NULL},
! 	{"Standby", standby_redo, standby_desc, standby_short_desc, NULL, NULL, NULL},
! 	{"Heap2", heap2_redo, heap2_desc, heap2_short_desc, NULL, NULL, NULL},
! 	{"Heap", heap_redo, heap_desc, heap_short_desc, NULL, NULL, NULL},
! 	{"Btree", btree_redo, btree_desc, btree_short_desc, btree_xlog_startup, btree_xlog_cleanup, btree_safe_restartpoint},
! 	{"Hash", hash_redo, hash_desc, hash_short_desc, NULL, NULL, NULL},
! 	{"Gin", gin_redo, gin_desc, gin_short_desc, gin_xlog_startup, gin_xlog_cleanup, gin_safe_restartpoint},
! 	{"Gist", gist_redo, gist_desc, gist_short_desc, gist_xlog_startup, gist_xlog_cleanup, NULL},
! 	{"Sequence", seq_redo, seq_desc, seq_short_desc, NULL, NULL, NULL},
! 	{"SPGist", spg_redo, spg_desc, spg_short_desc, spg_xlog_startup, spg_xlog_cleanup, NULL}
  };
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
new file mode 100644
index e22bdac..9fe744b
*** a/src/backend/access/transam/xact.c
--- b/src/backend/access/transam/xact.c
*************** xact_desc(StringInfo buf, uint8 xl_info,
*** 4972,4974 ****
--- 4972,5007 ----
  	else
  		appendStringInfo(buf, "UNKNOWN");
  }
+ 
+ void
+ xact_short_desc(char buf[50], uint8 xl_info)
+ {
+ 	uint8		info = xl_info & ~XLR_INFO_MASK;
+ 
+ 	switch (info)
+ 	{
+ 		case XLOG_XACT_COMMIT_COMPACT:
+ 			sprintf(buf, "commit compact");
+ 			break;
+ 		case XLOG_XACT_COMMIT:
+ 			sprintf(buf, "commit");
+ 			break;
+ 		case XLOG_XACT_ABORT:
+ 			sprintf(buf, "abort");
+ 			break;
+ 		case XLOG_XACT_PREPARE:
+ 			sprintf(buf, "prepare");
+ 			break;
+ 		case XLOG_XACT_COMMIT_PREPARED:
+ 			sprintf(buf, "commit prepared");
+ 			break;
+ 		case XLOG_XACT_ABORT_PREPARED:
+ 			sprintf(buf, "abort prepared");
+ 			break;
+ 		case XLOG_XACT_ASSIGNMENT:
+ 			sprintf(buf, "xid assignment xtop");
+ 			break;
+ 		default:
+ 			sprintf(buf, "UNKNOWN XACT INFO");
+ 	}
+ }
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
new file mode 100644
index ce659ec..5efcc0c
*** a/src/backend/access/transam/xlog.c
--- b/src/backend/access/transam/xlog.c
*************** StartupXLOG(void)
*** 6463,6468 ****
--- 6463,6470 ----
  			bool		recoveryPause = false;
  			ErrorContextCallback errcontext;
  			TimestampTz xtime;
+ 			TimestampTz before_ts,
+ 						after_ts;
  
  			InRedo = true;
  
*************** StartupXLOG(void)
*** 6559,6564 ****
--- 6561,6569 ----
  					TransactionIdIsValid(record->xl_xid))
  					RecordKnownAssignedTransactionIds(record->xl_xid);
  
+ 				if (pgstat_track_recovery)
+ 					before_ts = GetCurrentTimestamp();
+ 
  				RmgrTable[record->xl_rmid].rm_redo(EndRecPtr, record);
  
  				/* Pop the error context stack */
*************** StartupXLOG(void)
*** 6572,6577 ****
--- 6577,6589 ----
  				xlogctl->recoveryLastRecPtr = EndRecPtr;
  				SpinLockRelease(&xlogctl->info_lck);
  
+ 				if (pgstat_track_recovery)
+ 				{
+ 					after_ts = GetCurrentTimestamp();
+ 
+ 					pgstat_report_recovery_stats(record, before_ts, after_ts);
+ 				}
+ 
  				LastRec = ReadRecPtr;
  
  				record = ReadRecord(NULL, LOG, false);
*************** xlog_desc(StringInfo buf, uint8 xl_info,
*** 8656,8661 ****
--- 8668,8709 ----
  		appendStringInfo(buf, "UNKNOWN");
  }
  
+ void
+ xlog_short_desc(char buf[50], uint8 xl_info)
+ {
+ 	uint8		info = xl_info & ~XLR_INFO_MASK;
+ 
+ 	switch (info)
+ 	{
+ 		case XLOG_CHECKPOINT_SHUTDOWN:
+ 			sprintf(buf, "shutdown checkpoint");
+ 			break;
+ 		case XLOG_CHECKPOINT_ONLINE:
+ 			sprintf(buf, "online checkpoint");
+ 			break;
+ 		case XLOG_NOOP:
+ 			sprintf(buf, "xlog no-op");
+ 			break;
+ 		case XLOG_NEXTOID:
+ 			sprintf(buf, "nextOid");
+ 			break;
+ 		case XLOG_SWITCH:
+ 			sprintf(buf, "xlog switch");
+ 			break;
+ 		case XLOG_RESTORE_POINT:
+ 			sprintf(buf, "restore point");
+ 			break;
+ 		case XLOG_BACKUP_END:
+ 			sprintf(buf, "backup end");
+ 			break;
+ 		case XLOG_PARAMETER_CHANGE:
+ 			sprintf(buf, "parameter change");
+ 			break;
+ 		default:
+ 			sprintf(buf, "UNKNOWN XLOG INFO");
+ 	}
+ }
+ 
  #ifdef WAL_DEBUG
  
  static void
diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c
new file mode 100644
index 2e10d4d..bd89e91
*** a/src/backend/access/transam/xlogfuncs.c
--- b/src/backend/access/transam/xlogfuncs.c
***************
*** 23,28 ****
--- 23,29 ----
  #include "catalog/pg_type.h"
  #include "funcapi.h"
  #include "miscadmin.h"
+ #include "pgstat.h"
  #include "replication/walreceiver.h"
  #include "storage/smgr.h"
  #include "utils/builtins.h"
*************** pg_is_in_recovery(PG_FUNCTION_ARGS)
*** 465,467 ****
--- 466,556 ----
  {
  	PG_RETURN_BOOL(RecoveryInProgress());
  }
+ 
+ 
+ #define NUM_PG_STAT_RECOVERY_ATTS 11
+ Datum
+ pg_stat_get_recovery_activity(PG_FUNCTION_ARGS)
+ {
+ 	FuncCallContext *funcctx;
+ 
+ 	Datum		values[NUM_PG_STAT_RECOVERY_ATTS];
+ 	bool		nulls[NUM_PG_STAT_RECOVERY_ATTS];
+ 	HeapTuple	tuple;
+ 
+ 	PgStat_RecoveryStats *recoveryStats; 
+ 	PgStat_RecoveryStats *recoverystats_arr;
+ 
+ 	char 		info_string[50];
+ 
+     if (!RecoveryInProgress())
+         ereport(ERROR,
+                 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                  errmsg("recovery is not in progress")));
+ 
+ 	/* stuff done only on the first call of the function */
+ 	if (SRF_IS_FIRSTCALL())
+ 	{
+ 		TupleDesc	tupdesc;
+ 		MemoryContext oldcontext;
+ 
+ 		/* create a function context for cross-call persistence */
+ 		funcctx = SRF_FIRSTCALL_INIT();
+ 
+ 		/*
+ 		 * switch to memory context appropriate for multiple function calls
+ 		 */
+ 		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+ 
+ 		tupdesc = CreateTemplateTupleDesc(NUM_PG_STAT_RECOVERY_ATTS, false);
+ 		TupleDescInitEntry(tupdesc, (AttrNumber)  1, "rmgr", TEXTOID, -1, 0);
+ 		TupleDescInitEntry(tupdesc, (AttrNumber)  2, "wal_record_type", TEXTOID, -1, 0);
+ 		TupleDescInitEntry(tupdesc, (AttrNumber)  3, "n_records", INT4OID, -1, 0);
+ 		TupleDescInitEntry(tupdesc, (AttrNumber)  4, "n_bkp_blocks", INT4OID, -1, 0);
+ 		TupleDescInitEntry(tupdesc, (AttrNumber)  5, "tot_record_size", INT4OID, -1, 0);
+ 		TupleDescInitEntry(tupdesc, (AttrNumber)  6, "max_record_size", INT4OID, -1, 0);
+ 		TupleDescInitEntry(tupdesc, (AttrNumber)  7, "min_record_size", INT4OID, -1, 0);
+ 		TupleDescInitEntry(tupdesc, (AttrNumber)  8, "tot_redo_time", FLOAT8OID, -1, 0);
+ 		TupleDescInitEntry(tupdesc, (AttrNumber)  9, "max_redo_time", FLOAT8OID, -1, 0);
+ 		TupleDescInitEntry(tupdesc, (AttrNumber) 10, "min_redo_time", FLOAT8OID, -1, 0);
+ 		TupleDescInitEntry(tupdesc, (AttrNumber) 11, "last_process_time", TIMESTAMPTZOID, -1, 0);
+ 		funcctx->tuple_desc = BlessTupleDesc(tupdesc);
+ 
+ 		recoveryStats = (PgStat_RecoveryStats *) palloc0(PGSTAT_NUM_RECOVERYSTAT_ENTRIES * sizeof(PgStat_RecoveryStats));
+ 
+ 		funcctx->max_calls = pgstat_fetch_recoverystats(recoveryStats);
+ 		funcctx->user_fctx = (void *) recoveryStats;
+ 
+ 		MemoryContextSwitchTo(oldcontext);
+ 	}
+ 
+ 	/* stuff done on every call of the function */
+ 	funcctx = SRF_PERCALL_SETUP();
+ 
+ 	recoverystats_arr = (PgStat_RecoveryStats *) funcctx->user_fctx;
+ 
+ 	MemSet(values, 0, sizeof(values));
+ 	MemSet(nulls, 0, sizeof(nulls));
+ 
+ 	if (funcctx->call_cntr < funcctx->max_calls && pgstat_track_recovery)
+ 	{
+ 		values[0]  = CStringGetTextDatum(RmgrTable[recoverystats_arr[funcctx->call_cntr].rmid].rm_name);
+  		RmgrTable[recoverystats_arr[funcctx->call_cntr].rmid].rm_short_desc(info_string, recoverystats_arr[funcctx->call_cntr].info);
+  		values[1]  = CStringGetTextDatum(info_string);
+ 		values[2]  = Int32GetDatum(recoverystats_arr[funcctx->call_cntr].n_records);
+ 		values[3]  = Int32GetDatum(recoverystats_arr[funcctx->call_cntr].n_bkp_blocks);
+ 		values[4]  = Int32GetDatum(recoverystats_arr[funcctx->call_cntr].tot_record_size);
+ 		values[5]  = Int32GetDatum(recoverystats_arr[funcctx->call_cntr].max_record_size);
+ 		values[6]  = Int32GetDatum(recoverystats_arr[funcctx->call_cntr].min_record_size);
+ 		values[7]  = Float8GetDatum(recoverystats_arr[funcctx->call_cntr].tot_redo_time);
+ 		values[8]  = Float8GetDatum(recoverystats_arr[funcctx->call_cntr].max_redo_time);
+ 		values[9]  = Float8GetDatum(recoverystats_arr[funcctx->call_cntr].min_redo_time);
+ 		values[10] = TimestampTzGetDatum(recoverystats_arr[funcctx->call_cntr].last_process_time);
+ 		tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
+ 		SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));
+ 	}
+ 	else
+ 	{
+ 		SRF_RETURN_DONE(funcctx);
+ 	}
+ }
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
new file mode 100644
index a017101..6ceb93a
*** a/src/backend/catalog/storage.c
--- b/src/backend/catalog/storage.c
*************** smgr_desc(StringInfo buf, uint8 xl_info,
*** 553,555 ****
--- 553,573 ----
  	else
  		appendStringInfo(buf, "UNKNOWN");
  }
+ 
+ void
+ smgr_short_desc(char buf[50], uint8 xl_info)
+ {
+ 	uint8		info = xl_info & ~XLR_INFO_MASK;
+ 
+ 	switch(info)
+ 	{
+ 		case XLOG_SMGR_CREATE:
+ 			sprintf(buf, "file create");
+ 			break;
+ 		case XLOG_SMGR_TRUNCATE:
+ 			sprintf(buf, "file truncate");
+ 			break;
+ 		default:
+ 			sprintf(buf, "UNKNOWN SMGR INFO");
+ 	}
+ }
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
new file mode 100644
index 50ba20c..523a255
*** a/src/backend/catalog/system_views.sql
--- b/src/backend/catalog/system_views.sql
*************** CREATE VIEW pg_stat_database_conflicts A
*** 588,593 ****
--- 588,596 ----
              pg_stat_get_db_conflict_startup_deadlock(D.oid) AS confl_deadlock
      FROM pg_database D;
  
+ CREATE VIEW pg_stat_recovery AS
+     SELECT * FROM pg_stat_get_recovery_activity() AS R;
+ 
  CREATE VIEW pg_stat_user_functions AS
      SELECT
              P.oid AS funcid,
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
new file mode 100644
index 42a8b31..bae55aa
*** a/src/backend/commands/dbcommands.c
--- b/src/backend/commands/dbcommands.c
*************** dbase_desc(StringInfo buf, uint8 xl_info
*** 2000,2002 ****
--- 2000,2020 ----
  	else
  		appendStringInfo(buf, "UNKNOWN");
  }
+ 
+ void
+ dbase_short_desc(char buf[50], uint8 xl_info)
+ {
+ 	uint8		info = xl_info & ~XLR_INFO_MASK;
+ 
+ 	switch (info)
+ 	{
+ 		case XLOG_DBASE_CREATE:
+ 			sprintf(buf, "create db");
+ 			break;
+ 		case XLOG_DBASE_DROP:
+ 			sprintf(buf, "drop db");
+ 			break;
+ 		default:
+ 			sprintf(buf, "UNKNOWN DBASE INFO");
+ 	}
+ }
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
new file mode 100644
index f7712a9..2341deb
*** a/src/backend/commands/sequence.c
--- b/src/backend/commands/sequence.c
*************** seq_desc(StringInfo buf, uint8 xl_info,
*** 1564,1566 ****
--- 1564,1581 ----
  	appendStringInfo(buf, "rel %u/%u/%u",
  			   xlrec->node.spcNode, xlrec->node.dbNode, xlrec->node.relNode);
  }
+ 
+ void
+ seq_short_desc(char buf[50], uint8 xl_info)
+ {
+ 	uint8		info = xl_info & ~XLR_INFO_MASK;
+ 
+ 	switch (info)
+ 	{
+ 		case XLOG_SEQ_LOG:
+ 			sprintf(buf, "log");
+ 			break;
+ 		default:
+ 			sprintf(buf, "UNKNOWN SEQ INFO");
+ 	}
+ }
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
new file mode 100644
index ff58490..fcf8c90
*** a/src/backend/commands/tablespace.c
--- b/src/backend/commands/tablespace.c
*************** tblspc_desc(StringInfo buf, uint8 xl_inf
*** 1471,1473 ****
--- 1471,1491 ----
  	else
  		appendStringInfo(buf, "UNKNOWN");
  }
+ 
+ void
+ tblspc_short_desc(char buf[50], uint8 xl_info)
+ {
+ 	uint8		info = xl_info & ~XLR_INFO_MASK;
+ 
+ 	switch (info)
+ 	{
+ 		case XLOG_TBLSPC_CREATE:
+ 			sprintf(buf, "create tblspc");
+ 			break;
+ 		case XLOG_TBLSPC_DROP:
+ 			sprintf(buf, "drop tblspc");
+ 			break;
+ 		default:
+ 			sprintf(buf, "UNKNOWN TBLSPC INFO");
+ 	}
+ }
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
new file mode 100644
index 323d42b..030c522
*** a/src/backend/postmaster/pgstat.c
--- b/src/backend/postmaster/pgstat.c
***************
*** 41,46 ****
--- 41,47 ----
  #include "access/transam.h"
  #include "access/twophase_rmgr.h"
  #include "access/xact.h"
+ #include "access/xlog.h"
  #include "catalog/pg_database.h"
  #include "catalog/pg_proc.h"
  #include "libpq/ip.h"
***************
*** 108,113 ****
--- 109,115 ----
  #define PGSTAT_DB_HASH_SIZE		16
  #define PGSTAT_TAB_HASH_SIZE	512
  #define PGSTAT_FUNCTION_HASH_SIZE	512
+ #define PGSTAT_RECOVERY_HASH_SIZE	512
  
  
  /* ----------
***************
*** 116,121 ****
--- 118,124 ----
   */
  bool		pgstat_track_activities = false;
  bool		pgstat_track_counts = false;
+ bool		pgstat_track_recovery = false;
  int			pgstat_track_functions = TRACK_FUNC_OFF;
  int			pgstat_track_activity_query_size = 1024;
  
*************** static int	localNumBackends = 0;
*** 223,228 ****
--- 226,237 ----
   */
  static PgStat_GlobalStats globalStats;
  
+ /*
+  * Statistics about recovery, this is loaded as part of pgstat_read_statsfile()
+  */
+ static bool recoveryHashLoaded = false;
+ static HTAB *pgStatRecoveryHash = NULL;
+ 
  /* Last time the collector successfully wrote the stats file */
  static TimestampTz last_statwrite;
  
*************** static void pgstat_recv_bgwriter(PgStat_
*** 286,292 ****
  static void pgstat_recv_funcstat(PgStat_MsgFuncstat *msg, int len);
  static void pgstat_recv_funcpurge(PgStat_MsgFuncpurge *msg, int len);
  static void pgstat_recv_recoveryconflict(PgStat_MsgRecoveryConflict *msg, int len);
! 
  
  /* ------------------------------------------------------------
   * Public functions called from postmaster follow
--- 295,301 ----
  static void pgstat_recv_funcstat(PgStat_MsgFuncstat *msg, int len);
  static void pgstat_recv_funcpurge(PgStat_MsgFuncpurge *msg, int len);
  static void pgstat_recv_recoveryconflict(PgStat_MsgRecoveryConflict *msg, int len);
! static void pgstat_recv_recoverystat(PgStat_MsgRecoverystat *msg, int len);
  
  /* ------------------------------------------------------------
   * Public functions called from postmaster follow
*************** pgstat_report_recovery_conflict(int reas
*** 1339,1344 ****
--- 1348,1393 ----
  	pgstat_send(&msg, sizeof(msg));
  }
  
+ /* --------
+  * pgstat_report_recovery_stats() -
+  *
+  *	Tell the collector about recovery stats.
+  * --------
+  */
+ void
+ pgstat_report_recovery_stats(XLogRecord *record, TimestampTz before_ts, TimestampTz after_ts)
+ {
+ 	if (pgStatSock == PGINVALID_SOCKET || !pgstat_track_recovery)
+ 		return;
+ 	else
+ 	{
+ 		PgStat_MsgRecoverystat msg;
+ 		int		i;
+ 
+ 		msg.rmid = record->xl_rmid;
+ 		msg.info = record->xl_info;
+ 		msg.record_size = record->xl_tot_len;
+ 
+ 		/* the time the last record started redo process at */ 
+ 		msg.process_time = before_ts;
+ 		msg.redo_time = (double) after_ts - (double) before_ts;
+ 
+ 		/* get the number of backup blocks in this record */
+ 		msg.n_bkp_blocks = 0; 
+ 		if (record->xl_info & XLR_BKP_BLOCK_MASK)
+ 		{
+ 			for (i = 0; i < XLR_MAX_BKP_BLOCKS; i++)
+ 			{
+ 				if (!(record->xl_info & XLR_SET_BKP_BLOCK(i)))
+ 					msg.n_bkp_blocks += 1; 
+ 			}
+ 		}
+ 
+ 		pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_RECOVERYSTAT);
+ 		pgstat_send(&msg, sizeof(msg));
+ 	}
+ }
+ 
  /* ----------
   * pgstat_ping() -
   *
*************** pgstat_fetch_global(void)
*** 2218,2223 ****
--- 2267,2314 ----
  	return &globalStats;
  }
  
+ /*
+  * ---------
+  * pgstat_fetch_recoverystats() -
+  *
+  *	Support function for the SQL-callable pgstat* functions. Returns
+  *	a pointer to the recoverystats statistics struct.
+  * ---------
+  */
+ int 
+ pgstat_fetch_recoverystats(PgStat_RecoveryStats *recoveryStats)
+ {
+ 	int i = 0;
+ 	HASH_SEQ_STATUS rstat;
+ 	PgStat_RecoveryStats *recoveryentry;
+ 
+ 	/* load the stats file if needed */
+ 	backend_read_statsfile();
+ 
+ 	if (!pgStatRecoveryHash)
+ 		return 0;
+ 
+ 	/* Walk the whole hash table and fill the array */
+ 	hash_seq_init(&rstat, pgStatRecoveryHash);
+ 	while ((recoveryentry = (PgStat_RecoveryStats *) hash_seq_search(&rstat)) != NULL)
+ 	{
+ 		recoveryStats[i].rectypeid = recoveryentry->rectypeid;
+ 		recoveryStats[i].rmid = recoveryentry->rmid;
+ 		recoveryStats[i].info = recoveryentry->info;
+ 		recoveryStats[i].n_records = recoveryentry->n_records;
+ 		recoveryStats[i].n_bkp_blocks = recoveryentry->n_bkp_blocks;
+ 		recoveryStats[i].tot_record_size = recoveryentry->tot_record_size;
+ 		recoveryStats[i].max_record_size = recoveryentry->max_record_size;
+ 		recoveryStats[i].min_record_size = recoveryentry->min_record_size;
+ 		recoveryStats[i].tot_redo_time = recoveryentry->tot_redo_time;
+ 		recoveryStats[i].max_redo_time = recoveryentry->max_redo_time;
+ 		recoveryStats[i].min_redo_time = recoveryentry->min_redo_time;
+ 		recoveryStats[i].last_process_time = recoveryentry->last_process_time;
+ 		i++;
+ 	}
+ 
+ 	return i;
+ }
  
  /* ------------------------------------------------------------
   * Functions for management of the shared-memory PgBackendStatus array
*************** PgstatCollectorMain(int argc, char *argv
*** 3185,3190 ****
--- 3276,3285 ----
  					pgstat_recv_recoveryconflict((PgStat_MsgRecoveryConflict *) &msg, len);
  					break;
  
+ 				case PGSTAT_MTYPE_RECOVERYSTAT:
+ 					pgstat_recv_recoverystat((PgStat_MsgRecoverystat *) &msg, len);
+ 					break;
+ 
  				default:
  					break;
  			}
*************** pgstat_write_statsfile(bool permanent)
*** 3356,3364 ****
--- 3451,3461 ----
  	HASH_SEQ_STATUS hstat;
  	HASH_SEQ_STATUS tstat;
  	HASH_SEQ_STATUS fstat;
+ 	HASH_SEQ_STATUS rstat;
  	PgStat_StatDBEntry *dbentry;
  	PgStat_StatTabEntry *tabentry;
  	PgStat_StatFuncEntry *funcentry;
+ 	PgStat_RecoveryStats *recoveryentry;
  	FILE	   *fpout;
  	int32		format_id;
  	const char *tmpfile = permanent ? PGSTAT_STAT_PERMANENT_TMPFILE : pgstat_stat_tmpname;
*************** pgstat_write_statsfile(bool permanent)
*** 3440,3445 ****
--- 3537,3557 ----
  	}
  
  	/*
+ 	 * Walk through the recovery's stats table. This is done after write databases
+ 	 * and tables stats
+ 	 */
+ 	if (pgStatRecoveryHash)
+ 	{
+ 		hash_seq_init(&rstat, pgStatRecoveryHash);
+ 		while ((recoveryentry = (PgStat_RecoveryStats *) hash_seq_search(&rstat)) != NULL)
+ 		{
+ 			fputc('R', fpout);
+ 			rc = fwrite(recoveryentry, sizeof(PgStat_RecoveryStats), 1, fpout);
+ 			(void) rc;			/* we'll check for error with ferror */
+ 		}
+ 	}
+ 
+ 	/*
  	 * No more output to be done. Close the temp file and replace the old
  	 * pgstat.stat with it.  The ferror() check replaces testing for error
  	 * after each individual fputc or fwrite above.
*************** pgstat_read_statsfile(Oid onlydb, bool p
*** 3522,3527 ****
--- 3634,3641 ----
  	PgStat_StatTabEntry tabbuf;
  	PgStat_StatFuncEntry funcbuf;
  	PgStat_StatFuncEntry *funcentry;
+ 	PgStat_RecoveryStats recoverybuf;
+ 	PgStat_RecoveryStats *recoveryentry;
  	HASHCTL		hash_ctl;
  	HTAB	   *dbhash;
  	HTAB	   *tabhash = NULL;
*************** pgstat_read_statsfile(Oid onlydb, bool p
*** 3548,3553 ****
--- 3662,3682 ----
  						 HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
  
  	/*
+ 	 * And the recovery hashtable
+ 	 */
+ 	if (!pgStatRecoveryHash)
+ 	{
+ 		hash_ctl.keysize = sizeof(Oid);
+ 		hash_ctl.entrysize = sizeof(PgStat_RecoveryStats);
+ 		hash_ctl.hash = oid_hash;
+ 		hash_ctl.hcxt = pgStatLocalContext;
+ 		pgStatRecoveryHash = hash_create("Per-cluster recovery",
+ 								   PGSTAT_RECOVERY_HASH_SIZE,
+ 								   &hash_ctl,
+ 							       HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
+ 	}
+ 
+ 	/*
  	 * Clear out global statistics so they start from zero in case we can't
  	 * load an existing statsfile.
  	 */
*************** pgstat_read_statsfile(Oid onlydb, bool p
*** 3756,3761 ****
--- 3885,3922 ----
  				break;
  
  				/*
+ 				 * 'R'	A PgStat_RecoveryStats follows. This describes
+ 				 *      recovery activity
+ 				 */
+ 			case 'R':
+ 				/* if the table is already loaded avoid this */
+ 				if (!recoveryHashLoaded)
+ 				{
+ 					if (fread(&recoverybuf, 1, sizeof(PgStat_RecoveryStats),
+ 							  fpin) != sizeof(PgStat_RecoveryStats))
+ 					{
+ 						ereport(pgStatRunningInCollector ? LOG : WARNING,
+ 								(errmsg("corrupted statistics file \"%s\"",
+ 										statfile)));
+ 						goto done;
+ 					}
+ 
+ 					recoveryentry = (PgStat_RecoveryStats *) hash_search(pgStatRecoveryHash,
+ 													(void *) &(recoverybuf.rectypeid),
+ 															 HASH_ENTER, &found);
+ 	
+ 					if (found)
+ 					{
+ 						ereport(pgStatRunningInCollector ? LOG : WARNING,
+ 								(errmsg("corrupted statistics file \"%s\"",
+ 										statfile)));
+ 						goto done;
+ 					}
+ 					memcpy(recoveryentry, &recoverybuf, sizeof(recoverybuf));
+ 				}
+ 				break;
+ 
+ 				/*
  				 * 'E'	The EOF marker of a complete stats file.
  				 */
  			case 'E':
*************** pgstat_read_statsfile(Oid onlydb, bool p
*** 3772,3777 ****
--- 3933,3940 ----
  done:
  	FreeFile(fpin);
  
+ 	recoveryHashLoaded = true;
+ 
  	if (permanent)
  		unlink(PGSTAT_STAT_PERMANENT_FILENAME);
  
*************** pgstat_clear_snapshot(void)
*** 3944,3949 ****
--- 4107,4114 ----
  	/* Reset variables */
  	pgStatLocalContext = NULL;
  	pgStatDBHash = NULL;
+ 	recoveryHashLoaded = false;
+ 	pgStatRecoveryHash = NULL;
  	localBackendStatusTable = NULL;
  	localNumBackends = 0;
  }
*************** pgstat_recv_recoveryconflict(PgStat_MsgR
*** 4403,4408 ****
--- 4568,4633 ----
  }
  
  /* ----------
+  * pgstat_recv_recoverystat() -
+  *
+  *	Process as RECOVERYSTAT message.
+  * ----------
+  */
+ static void
+ pgstat_recv_recoverystat(PgStat_MsgRecoverystat *msg, int len)
+ {
+ 	int rectypeid = (msg->rmid * XLR_INFO_MASK) + msg->info;
+ 	PgStat_RecoveryStats *recoveryentry;
+ 	bool		found;
+ 
+ 	recoveryentry = (PgStat_RecoveryStats *) hash_search(pgStatRecoveryHash,
+ 														 (void *) &(rectypeid),
+ 														 HASH_ENTER, &found);
+ 
+ 	if (!found)
+ 	{
+ 		/*
+ 		 * If it's a new recovery entry, initialize counters to the values
+ 		 * we just got.
+ 		 */
+ 		recoveryentry->rectypeid = rectypeid;
+ 		recoveryentry->rmid = msg->rmid;
+ 		recoveryentry->info = msg->info;
+ 		recoveryentry->n_records = 1; 
+ 		recoveryentry->tot_record_size = msg->record_size; 
+ 		recoveryentry->max_record_size = msg->record_size; 
+ 		recoveryentry->min_record_size = msg->record_size; 
+ 		recoveryentry->tot_redo_time = msg->redo_time;
+ 		recoveryentry->max_redo_time = msg->redo_time;
+ 		recoveryentry->min_redo_time = msg->redo_time;
+ 		recoveryentry->n_bkp_blocks = msg->n_bkp_blocks; 
+ 	}
+ 	else
+ 	{
+ 		/*
+ 		 * Otherwise add the values to the existing entry.
+ 		 */
+ 		recoveryentry->n_records += 1; 
+ 		recoveryentry->n_bkp_blocks += msg->n_bkp_blocks; 
+ 
+ 		recoveryentry->tot_record_size += msg->record_size; 
+ 		if (msg->record_size > recoveryentry->max_record_size)
+ 			recoveryentry->max_record_size = msg->record_size; 
+ 		if (msg->record_size < recoveryentry->min_record_size)
+ 			recoveryentry->min_record_size = msg->record_size; 
+ 
+ 		recoveryentry->tot_redo_time += msg->redo_time;
+ 		if (msg->redo_time > recoveryentry->max_redo_time)
+ 			recoveryentry->max_redo_time = msg->redo_time;
+ 		if (msg->redo_time < recoveryentry->min_redo_time)
+ 			recoveryentry->min_redo_time = msg->redo_time;
+ 	}
+ 
+ 	/* common values */
+ 	recoveryentry->last_process_time = msg->process_time;
+ }
+ 
+ /* ----------
   * pgstat_recv_funcstat() -
   *
   *	Count what the backend has done.
diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c
new file mode 100644
index c88557c..fc773a6
*** a/src/backend/storage/ipc/standby.c
--- b/src/backend/storage/ipc/standby.c
*************** standby_desc(StringInfo buf, uint8 xl_in
*** 765,770 ****
--- 765,788 ----
  		appendStringInfo(buf, "UNKNOWN");
  }
  
+ void
+ standby_short_desc(char buf[50], uint8 xl_info)
+ {
+ 	uint8		info = xl_info & ~XLR_INFO_MASK;
+ 
+ 	switch (info)
+ 	{
+ 		case XLOG_STANDBY_LOCK:
+ 			sprintf(buf, "Standby AccessExclusive locks");
+ 			break;
+ 		case XLOG_RUNNING_XACTS:
+ 			sprintf(buf, "Standby running xacts");
+ 			break;
+ 		default:
+ 			sprintf(buf, "UNKNOWN STANDBY INFO");
+ 	}
+ }
+ 
  /*
   * Log details of the current snapshot to WAL. This allows the snapshot state
   * to be reconstructed on the standby.
diff --git a/src/backend/utils/cache/relmapper.c b/src/backend/utils/cache/relmapper.c
new file mode 100644
index 306832e..0f06421
*** a/src/backend/utils/cache/relmapper.c
--- b/src/backend/utils/cache/relmapper.c
*************** relmap_desc(StringInfo buf, uint8 xl_inf
*** 909,911 ****
--- 909,926 ----
  	else
  		appendStringInfo(buf, "UNKNOWN");
  }
+ 
+ void
+ relmap_short_desc(char buf[50], uint8 xl_info)
+ {
+ 	uint8		info = xl_info & ~XLR_INFO_MASK;
+ 
+ 	switch (info)
+ 	{
+ 		case XLOG_RELMAP_UPDATE:
+ 			sprintf(buf, "update relmap");
+ 			break;
+ 		default:
+ 			sprintf(buf, "UNKNOWN RELMAP INFO");
+ 	}
+ }
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
new file mode 100644
index 5c910dd..a3796e9
*** a/src/backend/utils/misc/guc.c
--- b/src/backend/utils/misc/guc.c
*************** static struct config_bool ConfigureNames
*** 1017,1022 ****
--- 1017,1031 ----
  		true,
  		NULL, NULL, NULL
  	},
+ 	{
+ 		{"track_recovery", PGC_POSTMASTER, STATS_COLLECTOR,
+ 			gettext_noop("Collects statistics on recovery activity."),
+ 			NULL
+ 		},
+ 		&pgstat_track_recovery,
+ 		false,
+ 		NULL, NULL, NULL
+ 	},
  
  	{
  		{"update_process_title", PGC_SUSET, STATS_COLLECTOR,
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
new file mode 100644
index 315db46..ebe2a98
*** a/src/backend/utils/misc/postgresql.conf.sample
--- b/src/backend/utils/misc/postgresql.conf.sample
***************
*** 420,425 ****
--- 420,426 ----
  
  #track_activities = on
  #track_counts = on
+ #track_recovery = off
  #track_functions = none			# none, pl, all
  #track_activity_query_size = 1024 	# (change requires restart)
  #update_process_title = on
diff --git a/src/include/access/clog.h b/src/include/access/clog.h
new file mode 100644
index bed3b8c..50af239
*** a/src/include/access/clog.h
--- b/src/include/access/clog.h
*************** extern void TruncateCLOG(TransactionId o
*** 49,53 ****
--- 49,54 ----
  
  extern void clog_redo(XLogRecPtr lsn, XLogRecord *record);
  extern void clog_desc(StringInfo buf, uint8 xl_info, char *rec);
+ extern void clog_short_desc(char buf[50], uint8 xl_info);
  
  #endif   /* CLOG_H */
diff --git a/src/include/access/gin.h b/src/include/access/gin.h
new file mode 100644
index 36e490a..76be479
*** a/src/include/access/gin.h
--- b/src/include/access/gin.h
*************** extern void ginUpdateStats(Relation inde
*** 56,61 ****
--- 56,62 ----
  /* ginxlog.c */
  extern void gin_redo(XLogRecPtr lsn, XLogRecord *record);
  extern void gin_desc(StringInfo buf, uint8 xl_info, char *rec);
+ extern void gin_short_desc(char buf[50], uint8 xl_info);
  extern void gin_xlog_startup(void);
  extern void gin_xlog_cleanup(void);
  extern bool gin_safe_restartpoint(void);
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
new file mode 100644
index 0d6b625..43fc364
*** a/src/include/access/gist_private.h
--- b/src/include/access/gist_private.h
*************** extern SplitedPageLayout *gistSplit(Rela
*** 458,463 ****
--- 458,464 ----
  /* gistxlog.c */
  extern void gist_redo(XLogRecPtr lsn, XLogRecord *record);
  extern void gist_desc(StringInfo buf, uint8 xl_info, char *rec);
+ extern void gist_short_desc(char buf[50], uint8 xl_info);
  extern void gist_xlog_startup(void);
  extern void gist_xlog_cleanup(void);
  
diff --git a/src/include/access/hash.h b/src/include/access/hash.h
new file mode 100644
index a3d0f98..6fe74fd
*** a/src/include/access/hash.h
--- b/src/include/access/hash.h
*************** extern OffsetNumber _hash_binsearch_last
*** 356,360 ****
--- 356,361 ----
  /* hash.c */
  extern void hash_redo(XLogRecPtr lsn, XLogRecord *record);
  extern void hash_desc(StringInfo buf, uint8 xl_info, char *rec);
+ extern void hash_short_desc(char buf[50], uint8 xl_info);
  
  #endif   /* HASH_H */
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
new file mode 100644
index fa38803..9f27fde
*** a/src/include/access/heapam.h
--- b/src/include/access/heapam.h
*************** extern void heap_sync(Relation relation)
*** 128,135 ****
--- 128,137 ----
  
  extern void heap_redo(XLogRecPtr lsn, XLogRecord *rptr);
  extern void heap_desc(StringInfo buf, uint8 xl_info, char *rec);
+ extern void heap_short_desc(char buf[50], uint8 xl_info);
  extern void heap2_redo(XLogRecPtr lsn, XLogRecord *rptr);
  extern void heap2_desc(StringInfo buf, uint8 xl_info, char *rec);
+ extern void heap2_short_desc(char buf[50], uint8 xl_info);
  
  extern XLogRecPtr log_heap_cleanup_info(RelFileNode rnode,
  					  TransactionId latestRemovedXid);
diff --git a/src/include/access/multixact.h b/src/include/access/multixact.h
new file mode 100644
index e20573d..329ed8b
*** a/src/include/access/multixact.h
--- b/src/include/access/multixact.h
*************** extern void multixact_twophase_postabort
*** 78,82 ****
--- 78,83 ----
  
  extern void multixact_redo(XLogRecPtr lsn, XLogRecord *record);
  extern void multixact_desc(StringInfo buf, uint8 xl_info, char *rec);
+ extern void multixact_short_desc(char buf[50], uint8 xl_info);
  
  #endif   /* MULTIXACT_H */
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
new file mode 100644
index 041733c..c5c80c9
*** a/src/include/access/nbtree.h
--- b/src/include/access/nbtree.h
*************** extern void _bt_leafbuild(BTSpool *btspo
*** 691,696 ****
--- 691,697 ----
   */
  extern void btree_redo(XLogRecPtr lsn, XLogRecord *record);
  extern void btree_desc(StringInfo buf, uint8 xl_info, char *rec);
+ extern void btree_short_desc(char buf[50], uint8 xl_info);
  extern void btree_xlog_startup(void);
  extern void btree_xlog_cleanup(void);
  extern bool btree_safe_restartpoint(void);
diff --git a/src/include/access/spgist.h b/src/include/access/spgist.h
new file mode 100644
index cd6de2c..ac528f6
*** a/src/include/access/spgist.h
--- b/src/include/access/spgist.h
*************** extern Datum spgvacuumcleanup(PG_FUNCTIO
*** 198,203 ****
--- 198,204 ----
  /* spgxlog.c */
  extern void spg_redo(XLogRecPtr lsn, XLogRecord *record);
  extern void spg_desc(StringInfo buf, uint8 xl_info, char *rec);
+ extern void spg_short_desc(char buf[50], uint8 xl_info);
  extern void spg_xlog_startup(void);
  extern void spg_xlog_cleanup(void);
  
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
new file mode 100644
index 5f063a9..1506031
*** a/src/include/access/xact.h
--- b/src/include/access/xact.h
*************** extern int	xactGetCommittedChildren(Tran
*** 248,252 ****
--- 248,253 ----
  
  extern void xact_redo(XLogRecPtr lsn, XLogRecord *record);
  extern void xact_desc(StringInfo buf, uint8 xl_info, char *rec);
+ extern void xact_short_desc(char buf[50], uint8 xl_info);
  
  #endif   /* XACT_H */
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
new file mode 100644
index 1ddf4bf..b455ed3
*** a/src/include/access/xlog.h
--- b/src/include/access/xlog.h
*************** extern void RestoreBkpBlocks(XLogRecPtr
*** 279,284 ****
--- 279,285 ----
  
  extern void xlog_redo(XLogRecPtr lsn, XLogRecord *record);
  extern void xlog_desc(StringInfo buf, uint8 xl_info, char *rec);
+ extern void xlog_short_desc(char buf[50], uint8 xl_info);
  
  extern void issue_xlog_fsync(int fd, uint32 log, uint32 seg);
  
diff --git a/src/include/access/xlog_internal.h b/src/include/access/xlog_internal.h
new file mode 100644
index db6380f..ee9b7c0
*** a/src/include/access/xlog_internal.h
--- b/src/include/access/xlog_internal.h
*************** typedef struct RmgrData
*** 250,255 ****
--- 250,256 ----
  	const char *rm_name;
  	void		(*rm_redo) (XLogRecPtr lsn, XLogRecord *rptr);
  	void		(*rm_desc) (StringInfo buf, uint8 xl_info, char *rec);
+ 	void		(*rm_short_desc) (char buf[], uint8 xl_info);
  	void		(*rm_startup) (void);
  	void		(*rm_cleanup) (void);
  	bool		(*rm_safe_restartpoint) (void);
*************** extern Datum pg_is_in_recovery(PG_FUNCTI
*** 281,285 ****
--- 282,287 ----
  extern Datum pg_xlog_replay_pause(PG_FUNCTION_ARGS);
  extern Datum pg_xlog_replay_resume(PG_FUNCTION_ARGS);
  extern Datum pg_is_xlog_replay_paused(PG_FUNCTION_ARGS);
+ extern Datum pg_stat_get_recovery_activity(PG_FUNCTION_ARGS);
  
  #endif   /* XLOG_INTERNAL_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
new file mode 100644
index 355c61a..227ba20
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DATA(insert OID = 2022 (  pg_stat_get_ac
*** 2577,2582 ****
--- 2577,2584 ----
  DESCR("statistics: information about currently active backends");
  DATA(insert OID = 3099 (  pg_stat_get_wal_senders	PGNSP PGUID 12 1 10 0 0 f f f f t s 0 0 2249 "" "{23,25,25,25,25,25,23,25}" "{o,o,o,o,o,o,o,o}" "{procpid,state,sent_location,write_location,flush_location,replay_location,sync_priority,sync_state}" _null_ pg_stat_get_wal_senders _null_ _null_ _null_ ));
  DESCR("statistics: information about currently active replication");
+ DATA(insert OID = 3144 (  pg_stat_get_recovery_activity			PGNSP PGUID 12 1 100 0 0 f f f f t s 0 0 2249 "" "{25,25,23,23,23,23,23,701,701,701,1184}" "{o,o,o,o,o,o,o,o,o,o,o}" "{rmgr,wal_record_type,n_records,n_bkp_blocks,tot_record_size,max_record_size,min_record_size,tot_redo_time,max_redo_time,min_redo_time,last_process_time}" _null_ pg_stat_get_recovery_activity _null_ _null_ _null_ ));
+ DESCR("statistics: information about recovery activity");
  DATA(insert OID = 2026 (  pg_backend_pid				PGNSP PGUID 12 1 0 0 0 f f f t f s 0 0 23 "" _null_ _null_ _null_ _null_ pg_backend_pid _null_ _null_ _null_ ));
  DESCR("statistics: current backend PID");
  DATA(insert OID = 1937 (  pg_stat_get_backend_pid		PGNSP PGUID 12 1 0 0 0 f f f t f s 1 0 23 "23" _null_ _null_ _null_ _null_ pg_stat_get_backend_pid _null_ _null_ _null_ ));
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
new file mode 100644
index d5103a8..62cdc16
*** a/src/include/catalog/storage.h
--- b/src/include/catalog/storage.h
*************** extern void log_smgrcreate(RelFileNode *
*** 38,42 ****
--- 38,43 ----
  
  extern void smgr_redo(XLogRecPtr lsn, XLogRecord *record);
  extern void smgr_desc(StringInfo buf, uint8 xl_info, char *rec);
+ extern void smgr_short_desc(char buf[50], uint8 xl_info);
  
  #endif   /* STORAGE_H */
diff --git a/src/include/commands/dbcommands.h b/src/include/commands/dbcommands.h
new file mode 100644
index 41ca8ff..13189cd
*** a/src/include/commands/dbcommands.h
--- b/src/include/commands/dbcommands.h
*************** extern char *get_database_name(Oid dbid)
*** 64,69 ****
--- 64,70 ----
  
  extern void dbase_redo(XLogRecPtr lsn, XLogRecord *rptr);
  extern void dbase_desc(StringInfo buf, uint8 xl_info, char *rec);
+ extern void dbase_short_desc(char buf[50], uint8 xl_info);
  
  extern void check_encoding_locale_matches(int encoding, const char *collate, const char *ctype);
  
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
new file mode 100644
index 2cdf86f..ffc6935
*** a/src/include/commands/sequence.h
--- b/src/include/commands/sequence.h
*************** extern void ResetSequence(Oid seq_relid)
*** 77,81 ****
--- 77,82 ----
  
  extern void seq_redo(XLogRecPtr lsn, XLogRecord *rptr);
  extern void seq_desc(StringInfo buf, uint8 xl_info, char *rec);
+ extern void seq_short_desc(char buf[50], uint8 xl_info);
  
  #endif   /* SEQUENCE_H */
diff --git a/src/include/commands/tablespace.h b/src/include/commands/tablespace.h
new file mode 100644
index fe8b6a5..c3ccd0e
*** a/src/include/commands/tablespace.h
--- b/src/include/commands/tablespace.h
*************** extern bool directory_is_empty(const cha
*** 58,62 ****
--- 58,63 ----
  
  extern void tblspc_redo(XLogRecPtr lsn, XLogRecord *rptr);
  extern void tblspc_desc(StringInfo buf, uint8 xl_info, char *rec);
+ extern void tblspc_short_desc(char buf[50], uint8 xl_info);
  
  #endif   /* TABLESPACE_H */
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
new file mode 100644
index b8c6d82..83b30f5
*** a/src/include/pgstat.h
--- b/src/include/pgstat.h
***************
*** 11,16 ****
--- 11,18 ----
  #ifndef PGSTAT_H
  #define PGSTAT_H
  
+ #include "access/rmgr.h"
+ #include "access/xlog.h"
  #include "datatype/timestamp.h"
  #include "fmgr.h"
  #include "libpq/pqcomm.h"
***************
*** 22,30 ****
  /* Values for track_functions GUC variable --- order is significant! */
  typedef enum TrackFunctionsLevel
  {
! 	TRACK_FUNC_OFF,
! 	TRACK_FUNC_PL,
! 	TRACK_FUNC_ALL
  }	TrackFunctionsLevel;
  
  /* ----------
--- 24,32 ----
  /* Values for track_functions GUC variable --- order is significant! */
  typedef enum TrackFunctionsLevel
  {
! TRACK_FUNC_OFF,
! TRACK_FUNC_PL,
! TRACK_FUNC_ALL
  }	TrackFunctionsLevel;
  
  /* ----------
*************** typedef enum StatMsgType
*** 47,53 ****
  	PGSTAT_MTYPE_BGWRITER,
  	PGSTAT_MTYPE_FUNCSTAT,
  	PGSTAT_MTYPE_FUNCPURGE,
! 	PGSTAT_MTYPE_RECOVERYCONFLICT
  } StatMsgType;
  
  /* ----------
--- 49,56 ----
  	PGSTAT_MTYPE_BGWRITER,
  	PGSTAT_MTYPE_FUNCSTAT,
  	PGSTAT_MTYPE_FUNCPURGE,
! 	PGSTAT_MTYPE_RECOVERYCONFLICT,
! 	PGSTAT_MTYPE_RECOVERYSTAT
  } StatMsgType;
  
  /* ----------
*************** typedef struct PgStat_MsgRecoveryConflic
*** 377,382 ****
--- 380,428 ----
  } PgStat_MsgRecoveryConflict;
  
  /* ----------
+  * PgStat_MsgRecoverystat	Sent by the backend upon recovery conflict
+  * ----------
+  */
+ 
+ #define PGSTAT_NUM_RECOVERYSTAT_ENTRIES 600 
+ 
+ /*
+  * Recovery statistics kept in both startup process and stats collector
+  */
+ typedef struct PgStat_RecoveryStats
+ {
+ 	/* 
+ 	 * rectypeid contains the full identification of the row which is 
+ 	 * just calculated by (rmid * XLR_INFO_MASK) + info.
+ 	 * it's kept apart just because it's a convenient key for the hash table
+      */
+ 	int				rectypeid;
+ 	RmgrId			rmid;
+ 	uint8			info;
+ 	PgStat_Counter	n_records;
+ 	PgStat_Counter	n_bkp_blocks;
+ 	int				tot_record_size;
+ 	int				max_record_size;
+ 	int				min_record_size;
+ 	double			tot_redo_time;
+ 	double			max_redo_time;
+ 	double			min_redo_time;
+ 	TimestampTz		last_process_time;
+ } PgStat_RecoveryStats;
+ 
+ typedef struct PgStat_MsgRecoverystat
+ {
+ 	PgStat_MsgHdr m_hdr;
+ 
+ 	RmgrId			rmid;
+ 	uint8			info;
+ 	PgStat_Counter	n_bkp_blocks;
+ 	int				record_size;
+ 	TimestampTz		process_time;
+ 	double			redo_time;
+ } PgStat_MsgRecoverystat;
+ 
+ /* ----------
   * PgStat_FunctionCounts	The actual per-function counts kept by a backend
   *
   * This struct should contain only actual event counters, because we memcmp
*************** typedef union PgStat_Msg
*** 472,477 ****
--- 518,524 ----
  	PgStat_MsgFuncstat msg_funcstat;
  	PgStat_MsgFuncpurge msg_funcpurge;
  	PgStat_MsgRecoveryConflict msg_recoveryconflict;
+ 	PgStat_MsgRecoverystat msg_recoverystat;
  } PgStat_Msg;
  
  
*************** typedef struct PgStat_FunctionCallUsage
*** 662,667 ****
--- 709,715 ----
   */
  extern bool pgstat_track_activities;
  extern bool pgstat_track_counts;
+ extern bool pgstat_track_recovery;
  extern int	pgstat_track_functions;
  extern PGDLLIMPORT int pgstat_track_activity_query_size;
  extern char *pgstat_stat_tmpname;
*************** extern void pgstat_report_analyze(Relati
*** 711,716 ****
--- 759,765 ----
  					  PgStat_Counter livetuples, PgStat_Counter deadtuples);
  
  extern void pgstat_report_recovery_conflict(int reason);
+ extern void pgstat_report_recovery_stats(XLogRecord *record, TimestampTz before_ts, TimestampTz after_ts);
  
  extern void pgstat_initialize(void);
  extern void pgstat_bestart(void);
*************** extern PgBackendStatus *pgstat_fetch_sta
*** 800,804 ****
--- 849,854 ----
  extern PgStat_StatFuncEntry *pgstat_fetch_stat_funcentry(Oid funcid);
  extern int	pgstat_fetch_stat_numbackends(void);
  extern PgStat_GlobalStats *pgstat_fetch_global(void);
+ extern int pgstat_fetch_recoverystats(PgStat_RecoveryStats *recoveryStats);
  
  #endif   /* PGSTAT_H */
diff --git a/src/include/storage/standby.h b/src/include/storage/standby.h
new file mode 100644
index a539ec2..64fc626
*** a/src/include/storage/standby.h
--- b/src/include/storage/standby.h
*************** typedef struct xl_running_xacts
*** 82,87 ****
--- 82,88 ----
  /* Recovery handlers for the Standby Rmgr (RM_STANDBY_ID) */
  extern void standby_redo(XLogRecPtr lsn, XLogRecord *record);
  extern void standby_desc(StringInfo buf, uint8 xl_info, char *rec);
+ extern void standby_short_desc(char buf[50], uint8 xl_info);
  
  /*
   * Declarations for GetRunningTransactionData(). Similar to Snapshots, but
diff --git a/src/include/utils/relmapper.h b/src/include/utils/relmapper.h
new file mode 100644
index 111a05c..d198a1f
*** a/src/include/utils/relmapper.h
--- b/src/include/utils/relmapper.h
*************** extern void RelationMapInitializePhase3(
*** 58,62 ****
--- 58,63 ----
  
  extern void relmap_redo(XLogRecPtr lsn, XLogRecord *record);
  extern void relmap_desc(StringInfo buf, uint8 xl_info, char *rec);
+ extern void relmap_short_desc(char buf[50], uint8 xl_info);
  
  #endif   /* RELMAPPER_H */
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
new file mode 100644
index 454e1f9..9773713
*** a/src/test/regress/expected/rules.out
--- b/src/test/regress/expected/rules.out
*************** SELECT viewname, definition FROM pg_view
*** 1298,1303 ****
--- 1298,1304 ----
   pg_stat_bgwriter                | SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints_timed, pg_stat_get_bgwriter_requested_checkpoints() AS checkpoints_req, pg_stat_get_bgwriter_buf_written_checkpoints() AS buffers_checkpoint, pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_written_backend() AS buffers_backend, pg_stat_get_buf_fsync_backend() AS buffers_backend_fsync, pg_stat_get_buf_alloc() AS buffers_alloc, pg_stat_get_bgwriter_stat_reset_time() AS stats_reset;
   pg_stat_database                | SELECT d.oid AS datid, d.datname, pg_stat_get_db_numbackends(d.oid) AS numbackends, pg_stat_get_db_xact_commit(d.oid) AS xact_commit, pg_stat_get_db_xact_rollback(d.oid) AS xact_rollback, (pg_stat_get_db_blocks_fetched(d.oid) - pg_stat_get_db_blocks_hit(d.oid)) AS blks_read, pg_stat_get_db_blocks_hit(d.oid) AS blks_hit, pg_stat_get_db_tuples_returned(d.oid) AS tup_returned, pg_stat_get_db_tuples_fetched(d.oid) AS tup_fetched, pg_stat_get_db_tuples_inserted(d.oid) AS tup_inserted, pg_stat_get_db_tuples_updated(d.oid) AS tup_updated, pg_stat_get_db_tuples_deleted(d.oid) AS tup_deleted, pg_stat_get_db_conflict_all(d.oid) AS conflicts, pg_stat_get_db_stat_reset_time(d.oid) AS stats_reset FROM pg_database d;
   pg_stat_database_conflicts      | SELECT d.oid AS datid, d.datname, pg_stat_get_db_conflict_tablespace(d.oid) AS confl_tablespace, pg_stat_get_db_conflict_lock(d.oid) AS confl_lock, pg_stat_get_db_conflict_snapshot(d.oid) AS confl_snapshot, pg_stat_get_db_conflict_bufferpin(d.oid) AS confl_bufferpin, pg_stat_get_db_conflict_startup_deadlock(d.oid) AS confl_deadlock FROM pg_database d;
+  pg_stat_recovery                | SELECT r.rmgr, r.wal_record_type, r.n_records, r.n_bkp_blocks, r.tot_record_size, r.max_record_size, r.min_record_size, r.tot_redo_time, r.max_redo_time, r.min_redo_time, r.last_process_time FROM pg_stat_get_recovery_activity() r(rmgr, wal_record_type, n_records, n_bkp_blocks, tot_record_size, max_record_size, min_record_size, tot_redo_time, max_redo_time, min_redo_time, last_process_time);
   pg_stat_replication             | SELECT s.procpid, s.usesysid, u.rolname AS usename, s.application_name, s.client_addr, s.client_hostname, s.client_port, s.backend_start, w.state, w.sent_location, w.write_location, w.flush_location, w.replay_location, w.sync_priority, w.sync_state FROM pg_stat_get_activity(NULL::integer) s(datid, procpid, usesysid, application_name, current_query, waiting, xact_start, query_start, backend_start, client_addr, client_hostname, client_port), pg_authid u, pg_stat_get_wal_senders() w(procpid, state, sent_location, write_location, flush_location, replay_location, sync_priority, sync_state) WHERE ((s.usesysid = u.oid) AND (s.procpid = w.procpid));
   pg_stat_sys_indexes             | SELECT pg_stat_all_indexes.relid, pg_stat_all_indexes.indexrelid, pg_stat_all_indexes.schemaname, pg_stat_all_indexes.relname, pg_stat_all_indexes.indexrelname, pg_stat_all_indexes.idx_scan, pg_stat_all_indexes.idx_tup_read, pg_stat_all_indexes.idx_tup_fetch FROM pg_stat_all_indexes WHERE ((pg_stat_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_indexes.schemaname ~ '^pg_toast'::text));
   pg_stat_sys_tables              | SELECT pg_stat_all_tables.relid, pg_stat_all_tables.schemaname, pg_stat_all_tables.relname, pg_stat_all_tables.seq_scan, pg_stat_all_tables.seq_tup_read, pg_stat_all_tables.idx_scan, pg_stat_all_tables.idx_tup_fetch, pg_stat_all_tables.n_tup_ins, pg_stat_all_tables.n_tup_upd, pg_stat_all_tables.n_tup_del, pg_stat_all_tables.n_tup_hot_upd, pg_stat_all_tables.n_live_tup, pg_stat_all_tables.n_dead_tup, pg_stat_all_tables.last_vacuum, pg_stat_all_tables.last_autovacuum, pg_stat_all_tables.last_analyze, pg_stat_all_tables.last_autoanalyze, pg_stat_all_tables.vacuum_count, pg_stat_all_tables.autovacuum_count, pg_stat_all_tables.analyze_count, pg_stat_all_tables.autoanalyze_count FROM pg_stat_all_tables WHERE ((pg_stat_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_tables.schemaname ~ '^pg_toast'::text));
*************** SELECT viewname, definition FROM pg_view
*** 1338,1344 ****
   shoelace_obsolete               | SELECT shoelace.sl_name, shoelace.sl_avail, shoelace.sl_color, shoelace.sl_len, shoelace.sl_unit, shoelace.sl_len_cm FROM shoelace WHERE (NOT (EXISTS (SELECT shoe.shoename FROM shoe WHERE (shoe.slcolor = shoelace.sl_color))));
   street                          | SELECT r.name, r.thepath, c.cname FROM ONLY road r, real_city c WHERE (c.outline ## r.thepath);
   toyemp                          | SELECT emp.name, emp.age, emp.location, (12 * emp.salary) AS annualsal FROM emp;
! (60 rows)
  
  SELECT tablename, rulename, definition FROM pg_rules
  	ORDER BY tablename, rulename;
--- 1339,1345 ----
   shoelace_obsolete               | SELECT shoelace.sl_name, shoelace.sl_avail, shoelace.sl_color, shoelace.sl_len, shoelace.sl_unit, shoelace.sl_len_cm FROM shoelace WHERE (NOT (EXISTS (SELECT shoe.shoename FROM shoe WHERE (shoe.slcolor = shoelace.sl_color))));
   street                          | SELECT r.name, r.thepath, c.cname FROM ONLY road r, real_city c WHERE (c.outline ## r.thepath);
   toyemp                          | SELECT emp.name, emp.age, emp.location, (12 * emp.salary) AS annualsal FROM emp;
! (61 rows)
  
  SELECT tablename, rulename, definition FROM pg_rules
  	ORDER BY tablename, rulename;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
new file mode 100644
index 7480ec4..9631674
*** a/src/tools/pgindent/typedefs.list
--- b/src/tools/pgindent/typedefs.list
*************** PgStat_MsgFuncstat
*** 1200,1211 ****
--- 1200,1213 ----
  PgStat_MsgHdr
  PgStat_MsgInquiry
  PgStat_MsgRecoveryConflict
+ PgStat_MsgRecoverystat
  PgStat_MsgResetcounter
  PgStat_MsgResetsharedcounter
  PgStat_MsgResetsinglecounter
  PgStat_MsgTabpurge
  PgStat_MsgTabstat
  PgStat_MsgVacuum
+ PgStat_RecoveryStats
  PgStat_Shared_Reset_Target
  PgStat_Single_Reset_Type
  PgStat_StatDBEntry
#2Bernd Helmle
mailings@oopsware.de
In reply to: Jaime Casanova (#1)
Re: pg_stats_recovery view

--On 15. Januar 2012 02:50:00 -0500 Jaime Casanova <jaime@2ndquadrant.com>
wrote:

Attached is a patch thats implements a pg_stat_recovery view that
keeps counters about processed wal records. I just notice that it
still lacks documentation but i will add it during the week.

Hi Jaime,

do you have an updated patch? The current v1 patch doesn't apply cleanly
anymore, and before i go and rebase the patch i thought i'm asking...

--
Thanks

Bernd

#3Jaime Casanova
jaime@2ndquadrant.com
In reply to: Bernd Helmle (#2)
1 attachment(s)
Re: pg_stats_recovery view

On Thu, Jan 26, 2012 at 4:03 AM, Bernd Helmle <mailings@oopsware.de> wrote:

--On 15. Januar 2012 02:50:00 -0500 Jaime Casanova <jaime@2ndquadrant.com>
wrote:

Attached is a patch thats implements a pg_stat_recovery view that
keeps counters about processed wal records. I just notice that it
still lacks documentation but i will add it during the week.

Hi Jaime,

do you have an updated patch? The current v1 patch doesn't apply cleanly
anymore, and before i go and rebase the patch i thought i'm asking...

here's the patch rebased to this morning's HEAD

--
Jaime Casanova         www.2ndQuadrant.com
Professional PostgreSQL: Soporte 24x7 y capacitación

Attachments:

stats_recovery.v1.rebased.patchtext/x-patch; charset=US-ASCII; name=stats_recovery.v1.rebased.patchDownload
diff --git a/src/backend/access/gin/ginxlog.c b/src/backend/access/gin/ginxlog.c
index 94051aa..9891ebb 100644
*** a/src/backend/access/gin/ginxlog.c
--- b/src/backend/access/gin/ginxlog.c
*************** gin_desc(StringInfo buf, uint8 xl_info,
*** 779,784 ****
--- 779,823 ----
  }
  
  void
+ gin_short_desc(char buf[50], uint8 xl_info)
+ {
+ 	uint8		info = xl_info & ~XLR_INFO_MASK;
+ 
+ 	switch (info)
+ 	{
+ 		case XLOG_GIN_CREATE_INDEX:
+ 			sprintf(buf, "Create index");
+ 			break;
+ 		case XLOG_GIN_CREATE_PTREE:
+ 			sprintf(buf, "Create posting tree");
+ 			break;
+ 		case XLOG_GIN_INSERT:
+ 			sprintf(buf, "Insert item");
+ 			break;
+ 		case XLOG_GIN_SPLIT:
+ 			sprintf(buf, "Page split");
+ 			break;
+ 		case XLOG_GIN_VACUUM_PAGE:
+ 			sprintf(buf, "Vacuum page");
+ 			break;
+ 		case XLOG_GIN_DELETE_PAGE:
+ 			sprintf(buf, "Delete page");
+ 			break;
+ 		case XLOG_GIN_UPDATE_META_PAGE:
+ 			sprintf(buf, "Update metapage");
+ 			break;
+ 		case XLOG_GIN_INSERT_LISTPAGE:
+ 			sprintf(buf, "Insert new list page");
+ 			break;
+ 		case XLOG_GIN_DELETE_LISTPAGE:
+ 			sprintf(buf, "Delete list pages");
+ 			break;
+ 		default:
+ 			sprintf(buf, "UNKNOWN GIN INFO");
+ 	}
+ }
+ 
+ void
  gin_xlog_startup(void)
  {
  	incomplete_splits = NIL;
diff --git a/src/backend/access/gist/gistxlog.c b/src/backend/access/gist/gistxlog.c
index 76029d9..397afaf 100644
*** a/src/backend/access/gist/gistxlog.c
--- b/src/backend/access/gist/gistxlog.c
*************** gist_desc(StringInfo buf, uint8 xl_info,
*** 394,399 ****
--- 394,424 ----
  }
  
  void
+ gist_short_desc(char buf[50], uint8 xl_info)
+ {
+ 	uint8		info = xl_info & ~XLR_INFO_MASK;
+ 
+ 	switch (info)
+ 	{
+ 		case XLOG_GIST_PAGE_UPDATE:
+ 			sprintf(buf, "page_update");
+ 			break;
+ 		case XLOG_GIST_PAGE_DELETE:
+ 			sprintf(buf, "page_delete");
+ 			break;
+ 		case XLOG_GIST_PAGE_SPLIT:
+ 			sprintf(buf, "page_split");
+ 			break;
+ 		case XLOG_GIST_CREATE_INDEX:
+ 			sprintf(buf, "create_index");
+ 			break;
+ 		default:
+ 			sprintf(buf, "UNKNOWN GIST INFO");
+ 			break;
+ 	}
+ }
+ 
+ void
  gist_xlog_startup(void)
  {
  	opCtx = createTempGistContext();
diff --git a/src/backend/access/hash/hash.c b/src/backend/access/hash/hash.c
index 8802669..f82a20a 100644
*** a/src/backend/access/hash/hash.c
--- b/src/backend/access/hash/hash.c
*************** void
*** 717,719 ****
--- 717,724 ----
  hash_desc(StringInfo buf, uint8 xl_info, char *rec)
  {
  }
+ 
+ void
+ hash_short_desc(char buf[50], uint8 xl_info)
+ {
+ }
diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c
index 99a431a..60ac3b7 100644
*** a/src/backend/access/heap/heapam.c
--- b/src/backend/access/heap/heapam.c
*************** heap2_desc(StringInfo buf, uint8 xl_info
*** 5709,5714 ****
--- 5709,5788 ----
  		appendStringInfo(buf, "UNKNOWN");
  }
  
+ void
+ heap_short_desc(char buf[50], uint8 xl_info)
+ {
+ 	uint8		info = xl_info & ~XLR_INFO_MASK;
+ 
+ 	info &= XLOG_HEAP_OPMASK;
+ 	switch (info)
+ 	{
+ 		case XLOG_HEAP_INSERT:
+ 			if (xl_info & XLOG_HEAP_INIT_PAGE)
+ 				sprintf(buf, "insert(init)");
+ 			else
+ 				sprintf(buf, "insert");
+ 			break;
+ 		case XLOG_HEAP_DELETE:
+ 			sprintf(buf, "delete");
+ 			break;
+ 		case XLOG_HEAP_UPDATE:
+ 			if (xl_info & XLOG_HEAP_INIT_PAGE)
+ 				sprintf(buf, "update(init)");
+ 			else
+ 				sprintf(buf, "update");
+ 			break;
+ 		case XLOG_HEAP_HOT_UPDATE:
+ 			if (xl_info & XLOG_HEAP_INIT_PAGE)		/* can this case happen? */
+ 				sprintf(buf, "hot_update(init)");
+ 			else
+ 				sprintf(buf, "hot_update");
+ 			break;
+ 		case XLOG_HEAP_NEWPAGE:
+ 			sprintf(buf, "newpage");
+ 			break;
+ 		case XLOG_HEAP_LOCK:
+ 			sprintf(buf, "lock");
+ 			break;
+ 		case XLOG_HEAP_INPLACE:
+ 			sprintf(buf, "inplace");
+ 			break;
+ 		default:
+ 			sprintf(buf, "UNKNOWN HEAP INFO");
+ 	}
+ }
+ 
+ void
+ heap2_short_desc(char buf[50], uint8 xl_info)
+ {
+ 	uint8		info = xl_info & ~XLR_INFO_MASK;
+ 
+ 	info &= XLOG_HEAP_OPMASK;
+ 	switch (info)
+ 	{
+ 		case XLOG_HEAP2_FREEZE:
+ 			sprintf(buf, "freeze");
+ 			break;
+ 		case XLOG_HEAP2_CLEAN:
+ 			sprintf(buf, "clean");
+ 			break;
+ 		case XLOG_HEAP2_CLEANUP_INFO:
+ 			sprintf(buf, "cleanup info");
+ 			break;	
+ 		case XLOG_HEAP2_VISIBLE:
+ 			sprintf(buf, "visible");
+ 			break;
+ 		case XLOG_HEAP2_MULTI_INSERT:
+ 			if (xl_info & XLOG_HEAP_INIT_PAGE)
+ 				sprintf(buf, "multi-insert (init)");
+ 			else
+ 				sprintf(buf, "multi-insert");
+ 			break;
+ 		default:
+ 			sprintf(buf, "UNKNOWN HEAP2 INFO");
+ 	}
+ }
+ 
  /*
   *	heap_sync		- sync a heap, for use when no WAL has been written
   *
diff --git a/src/backend/access/nbtree/nbtxlog.c b/src/backend/access/nbtree/nbtxlog.c
index 0f5c113..4b40e86 100644
*** a/src/backend/access/nbtree/nbtxlog.c
--- b/src/backend/access/nbtree/nbtxlog.c
*************** btree_desc(StringInfo buf, uint8 xl_info
*** 1179,1184 ****
--- 1179,1267 ----
  }
  
  void
+ btree_short_desc(char buf[50], uint8 xl_info)
+ {
+ 	uint8		info = xl_info & ~XLR_INFO_MASK;
+ 
+ 	switch (info)
+ 	{
+ 		case XLOG_BTREE_INSERT_LEAF:
+ 			{
+ 				sprintf(buf, "insert");
+ 				break;
+ 			}
+ 		case XLOG_BTREE_INSERT_UPPER:
+ 			{
+ 				sprintf(buf, "insert_upper");
+ 				break;
+ 			}
+ 		case XLOG_BTREE_INSERT_META:
+ 			{
+ 				sprintf(buf, "insert_meta");
+ 				break;
+ 			}
+ 		case XLOG_BTREE_SPLIT_L:
+ 			{
+ 				sprintf(buf, "split_l");
+ 				break;
+ 			}
+ 		case XLOG_BTREE_SPLIT_R:
+ 			{
+ 				sprintf(buf, "split_r");
+ 				break;
+ 			}
+ 		case XLOG_BTREE_SPLIT_L_ROOT:
+ 			{
+ 				sprintf(buf, "split_l_root");
+ 				break;
+ 			}
+ 		case XLOG_BTREE_SPLIT_R_ROOT:
+ 			{
+ 				sprintf(buf, "split_r_root");
+ 				break;
+ 			}
+ 		case XLOG_BTREE_VACUUM:
+ 			{
+ 				sprintf(buf, "vacuum");
+ 				break;
+ 			}
+ 		case XLOG_BTREE_DELETE:
+ 			{
+ 				sprintf(buf, "delete");
+ 				break;
+ 			}
+ 		case XLOG_BTREE_DELETE_PAGE:
+ 			{
+ 				sprintf(buf, "delete_page");
+ 				break;
+ 			}
+ 		case XLOG_BTREE_DELETE_PAGE_META:
+ 			{
+ 				sprintf(buf, "delete_page_meta");
+ 				break;
+ 			}
+ 		case XLOG_BTREE_DELETE_PAGE_HALF:
+ 			{
+ 				sprintf(buf, "delete_page_half");
+ 				break;
+ 			}
+ 		case XLOG_BTREE_NEWROOT:
+ 			{
+ 				sprintf(buf, "newroot");
+ 				break;
+ 			}
+ 		case XLOG_BTREE_REUSE_PAGE:
+ 			{
+ 				sprintf(buf, "reuse_page");
+ 				break;
+ 			}
+ 		default:
+ 			sprintf(buf, "UNKNOWN BTREE INFO");
+ 			break;
+ 	}
+ }
+ 
+ void
  btree_xlog_startup(void)
  {
  	incomplete_actions = NIL;
diff --git a/src/backend/access/spgist/spgxlog.c b/src/backend/access/spgist/spgxlog.c
index daa8ae3..fb0160d 100644
*** a/src/backend/access/spgist/spgxlog.c
--- b/src/backend/access/spgist/spgxlog.c
*************** spg_desc(StringInfo buf, uint8 xl_info,
*** 1053,1058 ****
--- 1053,1098 ----
  }
  
  void
+ spg_short_desc(char buf[50], uint8 xl_info)
+ {
+ 	uint8		info = xl_info & ~XLR_INFO_MASK;
+ 
+ 	switch (info)
+ 	{
+ 		case XLOG_SPGIST_CREATE_INDEX:
+ 			sprintf(buf, "create_index");
+ 			break;
+ 		case XLOG_SPGIST_ADD_LEAF:
+ 			sprintf(buf, "add leaf");
+ 			break;
+ 		case XLOG_SPGIST_MOVE_LEAFS:
+ 			sprintf(buf, "move leafs");
+ 			break;
+ 		case XLOG_SPGIST_ADD_NODE:
+ 			sprintf(buf, "add node");
+ 			break;
+ 		case XLOG_SPGIST_SPLIT_TUPLE:
+ 			sprintf(buf, "split node");
+ 			break;
+ 		case XLOG_SPGIST_PICKSPLIT:
+ 			sprintf(buf, "split leaf page");
+ 			break;
+ 		case XLOG_SPGIST_VACUUM_LEAF:
+ 			sprintf(buf, "vacuum leaf tuples on page");
+ 			break;
+ 		case XLOG_SPGIST_VACUUM_ROOT:
+ 			sprintf(buf, "vacuum leaf tuples on root page");
+ 			break;
+ 		case XLOG_SPGIST_VACUUM_REDIRECT:
+ 			sprintf(buf, "vacuum redirect tuples on page");
+ 			break;
+ 		default:
+ 			sprintf(buf, "UNKNOWN SPGIST INFO");
+ 			break;
+ 	}
+ }
+ 
+ void
  spg_xlog_startup(void)
  {
  	opCtx = AllocSetContextCreate(CurrentMemoryContext,
diff --git a/src/backend/access/transam/clog.c b/src/backend/access/transam/clog.c
index 69b6ef3..c2366ee 100644
*** a/src/backend/access/transam/clog.c
--- b/src/backend/access/transam/clog.c
*************** clog_desc(StringInfo buf, uint8 xl_info,
*** 790,792 ****
--- 790,810 ----
  	else
  		appendStringInfo(buf, "UNKNOWN");
  }
+ 
+ void
+ clog_short_desc(char buf[50], uint8 xl_info)
+ {
+ 	uint8		info = xl_info & ~XLR_INFO_MASK;
+ 
+ 	switch (info)
+ 	{
+ 		case CLOG_ZEROPAGE:
+ 			sprintf(buf, "zeropage");
+ 			break;
+ 		case CLOG_TRUNCATE:
+ 			sprintf(buf, "truncate before");
+ 			break;
+ 		default:
+ 			sprintf(buf, "UNKNOWN CLOG INFO");
+ 	}
+ }
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 454ca31..53b6552 100644
*** a/src/backend/access/transam/multixact.c
--- b/src/backend/access/transam/multixact.c
*************** multixact_desc(StringInfo buf, uint8 xl_
*** 2080,2082 ****
--- 2080,2103 ----
  	else
  		appendStringInfo(buf, "UNKNOWN");
  }
+ 
+ void
+ multixact_short_desc(char buf[50], uint8 xl_info)
+ {
+ 	uint8		info = xl_info & ~XLR_INFO_MASK;
+ 
+ 	switch (info)
+ 	{
+ 		case XLOG_MULTIXACT_ZERO_OFF_PAGE:
+ 			sprintf(buf, "zero offsets page");
+ 			break;
+ 		case XLOG_MULTIXACT_ZERO_MEM_PAGE:
+ 			sprintf(buf, "zero members page");
+ 			break;
+ 		case XLOG_MULTIXACT_CREATE_ID:
+ 			sprintf(buf, "create multixact");
+ 			break;
+ 		default:
+ 			sprintf(buf, "UNKNOWN MULTIXACT INFO");
+ 	}
+ }
diff --git a/src/backend/access/transam/rmgr.c b/src/backend/access/transam/rmgr.c
index ed8754e..1c27898 100644
*** a/src/backend/access/transam/rmgr.c
--- b/src/backend/access/transam/rmgr.c
***************
*** 26,46 ****
  
  
  const RmgrData RmgrTable[RM_MAX_ID + 1] = {
! 	{"XLOG", xlog_redo, xlog_desc, NULL, NULL, NULL},
! 	{"Transaction", xact_redo, xact_desc, NULL, NULL, NULL},
! 	{"Storage", smgr_redo, smgr_desc, NULL, NULL, NULL},
! 	{"CLOG", clog_redo, clog_desc, NULL, NULL, NULL},
! 	{"Database", dbase_redo, dbase_desc, NULL, NULL, NULL},
! 	{"Tablespace", tblspc_redo, tblspc_desc, NULL, NULL, NULL},
! 	{"MultiXact", multixact_redo, multixact_desc, NULL, NULL, NULL},
! 	{"RelMap", relmap_redo, relmap_desc, NULL, NULL, NULL},
! 	{"Standby", standby_redo, standby_desc, NULL, NULL, NULL},
! 	{"Heap2", heap2_redo, heap2_desc, NULL, NULL, NULL},
! 	{"Heap", heap_redo, heap_desc, NULL, NULL, NULL},
! 	{"Btree", btree_redo, btree_desc, btree_xlog_startup, btree_xlog_cleanup, btree_safe_restartpoint},
! 	{"Hash", hash_redo, hash_desc, NULL, NULL, NULL},
! 	{"Gin", gin_redo, gin_desc, gin_xlog_startup, gin_xlog_cleanup, gin_safe_restartpoint},
! 	{"Gist", gist_redo, gist_desc, gist_xlog_startup, gist_xlog_cleanup, NULL},
! 	{"Sequence", seq_redo, seq_desc, NULL, NULL, NULL},
! 	{"SPGist", spg_redo, spg_desc, spg_xlog_startup, spg_xlog_cleanup, NULL}
  };
--- 26,46 ----
  
  
  const RmgrData RmgrTable[RM_MAX_ID + 1] = {
! 	{"XLOG", xlog_redo, xlog_desc, xlog_short_desc, NULL, NULL, NULL},
! 	{"Transaction", xact_redo, xact_desc, xact_short_desc, NULL, NULL, NULL},
! 	{"Storage", smgr_redo, smgr_desc, smgr_short_desc, NULL, NULL, NULL},
! 	{"CLOG", clog_redo, clog_desc, clog_short_desc, NULL, NULL, NULL},
! 	{"Database", dbase_redo, dbase_desc, dbase_short_desc, NULL, NULL, NULL},
! 	{"Tablespace", tblspc_redo, tblspc_desc, tblspc_short_desc, NULL, NULL, NULL},
! 	{"MultiXact", multixact_redo, multixact_desc, multixact_short_desc, NULL, NULL, NULL},
! 	{"RelMap", relmap_redo, relmap_desc, relmap_short_desc, NULL, NULL, NULL},
! 	{"Standby", standby_redo, standby_desc, standby_short_desc, NULL, NULL, NULL},
! 	{"Heap2", heap2_redo, heap2_desc, heap2_short_desc, NULL, NULL, NULL},
! 	{"Heap", heap_redo, heap_desc, heap_short_desc, NULL, NULL, NULL},
! 	{"Btree", btree_redo, btree_desc, btree_short_desc, btree_xlog_startup, btree_xlog_cleanup, btree_safe_restartpoint},
! 	{"Hash", hash_redo, hash_desc, hash_short_desc, NULL, NULL, NULL},
! 	{"Gin", gin_redo, gin_desc, gin_short_desc, gin_xlog_startup, gin_xlog_cleanup, gin_safe_restartpoint},
! 	{"Gist", gist_redo, gist_desc, gist_short_desc, gist_xlog_startup, gist_xlog_cleanup, NULL},
! 	{"Sequence", seq_redo, seq_desc, seq_short_desc, NULL, NULL, NULL},
! 	{"SPGist", spg_redo, spg_desc, spg_short_desc, spg_xlog_startup, spg_xlog_cleanup, NULL}
  };
diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c
index e22bdac..9fe744b 100644
*** a/src/backend/access/transam/xact.c
--- b/src/backend/access/transam/xact.c
*************** xact_desc(StringInfo buf, uint8 xl_info,
*** 4972,4974 ****
--- 4972,5007 ----
  	else
  		appendStringInfo(buf, "UNKNOWN");
  }
+ 
+ void
+ xact_short_desc(char buf[50], uint8 xl_info)
+ {
+ 	uint8		info = xl_info & ~XLR_INFO_MASK;
+ 
+ 	switch (info)
+ 	{
+ 		case XLOG_XACT_COMMIT_COMPACT:
+ 			sprintf(buf, "commit compact");
+ 			break;
+ 		case XLOG_XACT_COMMIT:
+ 			sprintf(buf, "commit");
+ 			break;
+ 		case XLOG_XACT_ABORT:
+ 			sprintf(buf, "abort");
+ 			break;
+ 		case XLOG_XACT_PREPARE:
+ 			sprintf(buf, "prepare");
+ 			break;
+ 		case XLOG_XACT_COMMIT_PREPARED:
+ 			sprintf(buf, "commit prepared");
+ 			break;
+ 		case XLOG_XACT_ABORT_PREPARED:
+ 			sprintf(buf, "abort prepared");
+ 			break;
+ 		case XLOG_XACT_ASSIGNMENT:
+ 			sprintf(buf, "xid assignment xtop");
+ 			break;
+ 		default:
+ 			sprintf(buf, "UNKNOWN XACT INFO");
+ 	}
+ }
diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c
index 4b273a8..b797ebd 100644
*** a/src/backend/access/transam/xlog.c
--- b/src/backend/access/transam/xlog.c
*************** StartupXLOG(void)
*** 6538,6543 ****
--- 6538,6545 ----
  			bool		recoveryPause = false;
  			ErrorContextCallback errcontext;
  			TimestampTz xtime;
+ 			TimestampTz before_ts = 0;   /* keep compiler quiet */
+ 			TimestampTz after_ts;
  
  			InRedo = true;
  
*************** StartupXLOG(void)
*** 6634,6639 ****
--- 6636,6644 ----
  					TransactionIdIsValid(record->xl_xid))
  					RecordKnownAssignedTransactionIds(record->xl_xid);
  
+ 				if (pgstat_track_recovery)
+ 					before_ts = GetCurrentTimestamp();
+ 
  				RmgrTable[record->xl_rmid].rm_redo(EndRecPtr, record);
  
  				/* Pop the error context stack */
*************** StartupXLOG(void)
*** 6668,6673 ****
--- 6673,6685 ----
  				xlogctl->recoveryLastRecPtr = EndRecPtr;
  				SpinLockRelease(&xlogctl->info_lck);
  
+ 				if (pgstat_track_recovery)
+ 				{
+ 					after_ts = GetCurrentTimestamp();
+ 
+ 					pgstat_report_recovery_stats(record, before_ts, after_ts);
+ 				}
+ 
  				LastRec = ReadRecPtr;
  
  				record = ReadRecord(NULL, LOG, false);
*************** xlog_desc(StringInfo buf, uint8 xl_info,
*** 8838,8843 ****
--- 8850,8891 ----
  		appendStringInfo(buf, "UNKNOWN");
  }
  
+ void
+ xlog_short_desc(char buf[50], uint8 xl_info)
+ {
+ 	uint8		info = xl_info & ~XLR_INFO_MASK;
+ 
+ 	switch (info)
+ 	{
+ 		case XLOG_CHECKPOINT_SHUTDOWN:
+ 			sprintf(buf, "shutdown checkpoint");
+ 			break;
+ 		case XLOG_CHECKPOINT_ONLINE:
+ 			sprintf(buf, "online checkpoint");
+ 			break;
+ 		case XLOG_NOOP:
+ 			sprintf(buf, "xlog no-op");
+ 			break;
+ 		case XLOG_NEXTOID:
+ 			sprintf(buf, "nextOid");
+ 			break;
+ 		case XLOG_SWITCH:
+ 			sprintf(buf, "xlog switch");
+ 			break;
+ 		case XLOG_RESTORE_POINT:
+ 			sprintf(buf, "restore point");
+ 			break;
+ 		case XLOG_BACKUP_END:
+ 			sprintf(buf, "backup end");
+ 			break;
+ 		case XLOG_PARAMETER_CHANGE:
+ 			sprintf(buf, "parameter change");
+ 			break;
+ 		default:
+ 			sprintf(buf, "UNKNOWN XLOG INFO");
+ 	}
+ }
+ 
  #ifdef WAL_DEBUG
  
  static void
diff --git a/src/backend/access/transam/xlogfuncs.c b/src/backend/access/transam/xlogfuncs.c
index 2e10d4d..bd89e91 100644
*** a/src/backend/access/transam/xlogfuncs.c
--- b/src/backend/access/transam/xlogfuncs.c
***************
*** 23,28 ****
--- 23,29 ----
  #include "catalog/pg_type.h"
  #include "funcapi.h"
  #include "miscadmin.h"
+ #include "pgstat.h"
  #include "replication/walreceiver.h"
  #include "storage/smgr.h"
  #include "utils/builtins.h"
*************** pg_is_in_recovery(PG_FUNCTION_ARGS)
*** 465,467 ****
--- 466,556 ----
  {
  	PG_RETURN_BOOL(RecoveryInProgress());
  }
+ 
+ 
+ #define NUM_PG_STAT_RECOVERY_ATTS 11
+ Datum
+ pg_stat_get_recovery_activity(PG_FUNCTION_ARGS)
+ {
+ 	FuncCallContext *funcctx;
+ 
+ 	Datum		values[NUM_PG_STAT_RECOVERY_ATTS];
+ 	bool		nulls[NUM_PG_STAT_RECOVERY_ATTS];
+ 	HeapTuple	tuple;
+ 
+ 	PgStat_RecoveryStats *recoveryStats; 
+ 	PgStat_RecoveryStats *recoverystats_arr;
+ 
+ 	char 		info_string[50];
+ 
+     if (!RecoveryInProgress())
+         ereport(ERROR,
+                 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+                  errmsg("recovery is not in progress")));
+ 
+ 	/* stuff done only on the first call of the function */
+ 	if (SRF_IS_FIRSTCALL())
+ 	{
+ 		TupleDesc	tupdesc;
+ 		MemoryContext oldcontext;
+ 
+ 		/* create a function context for cross-call persistence */
+ 		funcctx = SRF_FIRSTCALL_INIT();
+ 
+ 		/*
+ 		 * switch to memory context appropriate for multiple function calls
+ 		 */
+ 		oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+ 
+ 		tupdesc = CreateTemplateTupleDesc(NUM_PG_STAT_RECOVERY_ATTS, false);
+ 		TupleDescInitEntry(tupdesc, (AttrNumber)  1, "rmgr", TEXTOID, -1, 0);
+ 		TupleDescInitEntry(tupdesc, (AttrNumber)  2, "wal_record_type", TEXTOID, -1, 0);
+ 		TupleDescInitEntry(tupdesc, (AttrNumber)  3, "n_records", INT4OID, -1, 0);
+ 		TupleDescInitEntry(tupdesc, (AttrNumber)  4, "n_bkp_blocks", INT4OID, -1, 0);
+ 		TupleDescInitEntry(tupdesc, (AttrNumber)  5, "tot_record_size", INT4OID, -1, 0);
+ 		TupleDescInitEntry(tupdesc, (AttrNumber)  6, "max_record_size", INT4OID, -1, 0);
+ 		TupleDescInitEntry(tupdesc, (AttrNumber)  7, "min_record_size", INT4OID, -1, 0);
+ 		TupleDescInitEntry(tupdesc, (AttrNumber)  8, "tot_redo_time", FLOAT8OID, -1, 0);
+ 		TupleDescInitEntry(tupdesc, (AttrNumber)  9, "max_redo_time", FLOAT8OID, -1, 0);
+ 		TupleDescInitEntry(tupdesc, (AttrNumber) 10, "min_redo_time", FLOAT8OID, -1, 0);
+ 		TupleDescInitEntry(tupdesc, (AttrNumber) 11, "last_process_time", TIMESTAMPTZOID, -1, 0);
+ 		funcctx->tuple_desc = BlessTupleDesc(tupdesc);
+ 
+ 		recoveryStats = (PgStat_RecoveryStats *) palloc0(PGSTAT_NUM_RECOVERYSTAT_ENTRIES * sizeof(PgStat_RecoveryStats));
+ 
+ 		funcctx->max_calls = pgstat_fetch_recoverystats(recoveryStats);
+ 		funcctx->user_fctx = (void *) recoveryStats;
+ 
+ 		MemoryContextSwitchTo(oldcontext);
+ 	}
+ 
+ 	/* stuff done on every call of the function */
+ 	funcctx = SRF_PERCALL_SETUP();
+ 
+ 	recoverystats_arr = (PgStat_RecoveryStats *) funcctx->user_fctx;
+ 
+ 	MemSet(values, 0, sizeof(values));
+ 	MemSet(nulls, 0, sizeof(nulls));
+ 
+ 	if (funcctx->call_cntr < funcctx->max_calls && pgstat_track_recovery)
+ 	{
+ 		values[0]  = CStringGetTextDatum(RmgrTable[recoverystats_arr[funcctx->call_cntr].rmid].rm_name);
+  		RmgrTable[recoverystats_arr[funcctx->call_cntr].rmid].rm_short_desc(info_string, recoverystats_arr[funcctx->call_cntr].info);
+  		values[1]  = CStringGetTextDatum(info_string);
+ 		values[2]  = Int32GetDatum(recoverystats_arr[funcctx->call_cntr].n_records);
+ 		values[3]  = Int32GetDatum(recoverystats_arr[funcctx->call_cntr].n_bkp_blocks);
+ 		values[4]  = Int32GetDatum(recoverystats_arr[funcctx->call_cntr].tot_record_size);
+ 		values[5]  = Int32GetDatum(recoverystats_arr[funcctx->call_cntr].max_record_size);
+ 		values[6]  = Int32GetDatum(recoverystats_arr[funcctx->call_cntr].min_record_size);
+ 		values[7]  = Float8GetDatum(recoverystats_arr[funcctx->call_cntr].tot_redo_time);
+ 		values[8]  = Float8GetDatum(recoverystats_arr[funcctx->call_cntr].max_redo_time);
+ 		values[9]  = Float8GetDatum(recoverystats_arr[funcctx->call_cntr].min_redo_time);
+ 		values[10] = TimestampTzGetDatum(recoverystats_arr[funcctx->call_cntr].last_process_time);
+ 		tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
+ 		SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple));
+ 	}
+ 	else
+ 	{
+ 		SRF_RETURN_DONE(funcctx);
+ 	}
+ }
diff --git a/src/backend/catalog/storage.c b/src/backend/catalog/storage.c
index a017101..6ceb93a 100644
*** a/src/backend/catalog/storage.c
--- b/src/backend/catalog/storage.c
*************** smgr_desc(StringInfo buf, uint8 xl_info,
*** 553,555 ****
--- 553,573 ----
  	else
  		appendStringInfo(buf, "UNKNOWN");
  }
+ 
+ void
+ smgr_short_desc(char buf[50], uint8 xl_info)
+ {
+ 	uint8		info = xl_info & ~XLR_INFO_MASK;
+ 
+ 	switch(info)
+ 	{
+ 		case XLOG_SMGR_CREATE:
+ 			sprintf(buf, "file create");
+ 			break;
+ 		case XLOG_SMGR_TRUNCATE:
+ 			sprintf(buf, "file truncate");
+ 			break;
+ 		default:
+ 			sprintf(buf, "UNKNOWN SMGR INFO");
+ 	}
+ }
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index 30b0bd0..0a825ce 100644
*** a/src/backend/catalog/system_views.sql
--- b/src/backend/catalog/system_views.sql
*************** CREATE VIEW pg_stat_database_conflicts A
*** 593,598 ****
--- 593,601 ----
              pg_stat_get_db_conflict_startup_deadlock(D.oid) AS confl_deadlock
      FROM pg_database D;
  
+ CREATE VIEW pg_stat_recovery AS
+     SELECT * FROM pg_stat_get_recovery_activity() AS R;
+ 
  CREATE VIEW pg_stat_user_functions AS
      SELECT
              P.oid AS funcid,
diff --git a/src/backend/commands/dbcommands.c b/src/backend/commands/dbcommands.c
index 42a8b31..bae55aa 100644
*** a/src/backend/commands/dbcommands.c
--- b/src/backend/commands/dbcommands.c
*************** dbase_desc(StringInfo buf, uint8 xl_info
*** 2000,2002 ****
--- 2000,2020 ----
  	else
  		appendStringInfo(buf, "UNKNOWN");
  }
+ 
+ void
+ dbase_short_desc(char buf[50], uint8 xl_info)
+ {
+ 	uint8		info = xl_info & ~XLR_INFO_MASK;
+ 
+ 	switch (info)
+ 	{
+ 		case XLOG_DBASE_CREATE:
+ 			sprintf(buf, "create db");
+ 			break;
+ 		case XLOG_DBASE_DROP:
+ 			sprintf(buf, "drop db");
+ 			break;
+ 		default:
+ 			sprintf(buf, "UNKNOWN DBASE INFO");
+ 	}
+ }
diff --git a/src/backend/commands/sequence.c b/src/backend/commands/sequence.c
index d3739cb..0a13e62 100644
*** a/src/backend/commands/sequence.c
--- b/src/backend/commands/sequence.c
*************** seq_desc(StringInfo buf, uint8 xl_info,
*** 1572,1574 ****
--- 1572,1589 ----
  	appendStringInfo(buf, "rel %u/%u/%u",
  			   xlrec->node.spcNode, xlrec->node.dbNode, xlrec->node.relNode);
  }
+ 
+ void
+ seq_short_desc(char buf[50], uint8 xl_info)
+ {
+ 	uint8		info = xl_info & ~XLR_INFO_MASK;
+ 
+ 	switch (info)
+ 	{
+ 		case XLOG_SEQ_LOG:
+ 			sprintf(buf, "log");
+ 			break;
+ 		default:
+ 			sprintf(buf, "UNKNOWN SEQ INFO");
+ 	}
+ }
diff --git a/src/backend/commands/tablespace.c b/src/backend/commands/tablespace.c
index ff58490..fcf8c90 100644
*** a/src/backend/commands/tablespace.c
--- b/src/backend/commands/tablespace.c
*************** tblspc_desc(StringInfo buf, uint8 xl_inf
*** 1471,1473 ****
--- 1471,1491 ----
  	else
  		appendStringInfo(buf, "UNKNOWN");
  }
+ 
+ void
+ tblspc_short_desc(char buf[50], uint8 xl_info)
+ {
+ 	uint8		info = xl_info & ~XLR_INFO_MASK;
+ 
+ 	switch (info)
+ 	{
+ 		case XLOG_TBLSPC_CREATE:
+ 			sprintf(buf, "create tblspc");
+ 			break;
+ 		case XLOG_TBLSPC_DROP:
+ 			sprintf(buf, "drop tblspc");
+ 			break;
+ 		default:
+ 			sprintf(buf, "UNKNOWN TBLSPC INFO");
+ 	}
+ }
diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c
index a53fc52..34e9874 100644
*** a/src/backend/postmaster/pgstat.c
--- b/src/backend/postmaster/pgstat.c
***************
*** 41,46 ****
--- 41,47 ----
  #include "access/transam.h"
  #include "access/twophase_rmgr.h"
  #include "access/xact.h"
+ #include "access/xlog.h"
  #include "catalog/pg_database.h"
  #include "catalog/pg_proc.h"
  #include "libpq/ip.h"
***************
*** 108,113 ****
--- 109,115 ----
  #define PGSTAT_DB_HASH_SIZE		16
  #define PGSTAT_TAB_HASH_SIZE	512
  #define PGSTAT_FUNCTION_HASH_SIZE	512
+ #define PGSTAT_RECOVERY_HASH_SIZE	512
  
  
  /* ----------
***************
*** 116,121 ****
--- 118,124 ----
   */
  bool		pgstat_track_activities = false;
  bool		pgstat_track_counts = false;
+ bool		pgstat_track_recovery = false;
  int			pgstat_track_functions = TRACK_FUNC_OFF;
  int			pgstat_track_activity_query_size = 1024;
  
*************** static int	localNumBackends = 0;
*** 223,228 ****
--- 226,237 ----
   */
  static PgStat_GlobalStats globalStats;
  
+ /*
+  * Statistics about recovery, this is loaded as part of pgstat_read_statsfile()
+  */
+ static bool recoveryHashLoaded = false;
+ static HTAB *pgStatRecoveryHash = NULL;
+ 
  /* Last time the collector successfully wrote the stats file */
  static TimestampTz last_statwrite;
  
*************** static void pgstat_recv_bgwriter(PgStat_
*** 286,291 ****
--- 295,301 ----
  static void pgstat_recv_funcstat(PgStat_MsgFuncstat *msg, int len);
  static void pgstat_recv_funcpurge(PgStat_MsgFuncpurge *msg, int len);
  static void pgstat_recv_recoveryconflict(PgStat_MsgRecoveryConflict *msg, int len);
+ static void pgstat_recv_recoverystat(PgStat_MsgRecoverystat *msg, int len);
  static void pgstat_recv_deadlock(PgStat_MsgDeadlock *msg, int len);
  static void pgstat_recv_tempfile(PgStat_MsgTempFile *msg, int len);
  
*************** pgstat_report_recovery_conflict(int reas
*** 1341,1346 ****
--- 1351,1398 ----
  	pgstat_send(&msg, sizeof(msg));
  }
  
+ 
+ /* --------
+  * pgstat_report_recovery_stats() -
+  *
+  *	Tell the collector about recovery stats.
+  * --------
+  */
+ void
+ pgstat_report_recovery_stats(XLogRecord *record, TimestampTz before_ts, TimestampTz after_ts)
+ {
+ 	if (pgStatSock == PGINVALID_SOCKET || !pgstat_track_recovery)
+ 		return;
+ 	else
+ 	{
+ 		PgStat_MsgRecoverystat msg;
+ 		int		i;
+ 
+ 		msg.rmid = record->xl_rmid;
+ 		msg.info = record->xl_info;
+ 		msg.record_size = record->xl_tot_len;
+ 
+ 		/* the time the last record started redo process at */ 
+ 		msg.process_time = before_ts;
+ 		msg.redo_time = (double) after_ts - (double) before_ts;
+ 
+ 		/* get the number of backup blocks in this record */
+ 		msg.n_bkp_blocks = 0; 
+ 		if (record->xl_info & XLR_BKP_BLOCK_MASK)
+ 		{
+ 			for (i = 0; i < XLR_MAX_BKP_BLOCKS; i++)
+ 			{
+ 				if (!(record->xl_info & XLR_SET_BKP_BLOCK(i)))
+ 					msg.n_bkp_blocks += 1; 
+ 			}
+ 		}
+ 
+ 		pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_RECOVERYSTAT);
+ 		pgstat_send(&msg, sizeof(msg));
+ 	}
+ }
+ 
+ 
  /* --------
   * pgstat_report_deadlock() -
   *
*************** pgstat_fetch_global(void)
*** 2260,2265 ****
--- 2312,2359 ----
  	return &globalStats;
  }
  
+ /*
+  * ---------
+  * pgstat_fetch_recoverystats() -
+  *
+  *	Support function for the SQL-callable pgstat* functions. Returns
+  *	a pointer to the recoverystats statistics struct.
+  * ---------
+  */
+ int 
+ pgstat_fetch_recoverystats(PgStat_RecoveryStats *recoveryStats)
+ {
+ 	int i = 0;
+ 	HASH_SEQ_STATUS rstat;
+ 	PgStat_RecoveryStats *recoveryentry;
+ 
+ 	/* load the stats file if needed */
+ 	backend_read_statsfile();
+ 
+ 	if (!pgStatRecoveryHash)
+ 		return 0;
+ 
+ 	/* Walk the whole hash table and fill the array */
+ 	hash_seq_init(&rstat, pgStatRecoveryHash);
+ 	while ((recoveryentry = (PgStat_RecoveryStats *) hash_seq_search(&rstat)) != NULL)
+ 	{
+ 		recoveryStats[i].rectypeid = recoveryentry->rectypeid;
+ 		recoveryStats[i].rmid = recoveryentry->rmid;
+ 		recoveryStats[i].info = recoveryentry->info;
+ 		recoveryStats[i].n_records = recoveryentry->n_records;
+ 		recoveryStats[i].n_bkp_blocks = recoveryentry->n_bkp_blocks;
+ 		recoveryStats[i].tot_record_size = recoveryentry->tot_record_size;
+ 		recoveryStats[i].max_record_size = recoveryentry->max_record_size;
+ 		recoveryStats[i].min_record_size = recoveryentry->min_record_size;
+ 		recoveryStats[i].tot_redo_time = recoveryentry->tot_redo_time;
+ 		recoveryStats[i].max_redo_time = recoveryentry->max_redo_time;
+ 		recoveryStats[i].min_redo_time = recoveryentry->min_redo_time;
+ 		recoveryStats[i].last_process_time = recoveryentry->last_process_time;
+ 		i++;
+ 	}
+ 
+ 	return i;
+ }
  
  /* ------------------------------------------------------------
   * Functions for management of the shared-memory PgBackendStatus array
*************** PgstatCollectorMain(int argc, char *argv
*** 3260,3265 ****
--- 3354,3363 ----
  					pgstat_recv_recoveryconflict((PgStat_MsgRecoveryConflict *) &msg, len);
  					break;
  
+ 				case PGSTAT_MTYPE_RECOVERYSTAT:
+ 					pgstat_recv_recoverystat((PgStat_MsgRecoverystat *) &msg, len);
+ 					break;
+ 
  				case PGSTAT_MTYPE_DEADLOCK:
  					pgstat_recv_deadlock((PgStat_MsgDeadlock *) &msg, len);
  					break;
*************** pgstat_write_statsfile(bool permanent)
*** 3442,3450 ****
--- 3540,3550 ----
  	HASH_SEQ_STATUS hstat;
  	HASH_SEQ_STATUS tstat;
  	HASH_SEQ_STATUS fstat;
+ 	HASH_SEQ_STATUS rstat;
  	PgStat_StatDBEntry *dbentry;
  	PgStat_StatTabEntry *tabentry;
  	PgStat_StatFuncEntry *funcentry;
+ 	PgStat_RecoveryStats *recoveryentry;
  	FILE	   *fpout;
  	int32		format_id;
  	const char *tmpfile = permanent ? PGSTAT_STAT_PERMANENT_TMPFILE : pgstat_stat_tmpname;
*************** pgstat_write_statsfile(bool permanent)
*** 3526,3531 ****
--- 3626,3646 ----
  	}
  
  	/*
+ 	 * Walk through the recovery's stats table. This is done after write databases
+ 	 * and tables stats
+ 	 */
+ 	if (pgStatRecoveryHash)
+ 	{
+ 		hash_seq_init(&rstat, pgStatRecoveryHash);
+ 		while ((recoveryentry = (PgStat_RecoveryStats *) hash_seq_search(&rstat)) != NULL)
+ 		{
+ 			fputc('R', fpout);
+ 			rc = fwrite(recoveryentry, sizeof(PgStat_RecoveryStats), 1, fpout);
+ 			(void) rc;			/* we'll check for error with ferror */
+ 		}
+ 	}
+ 
+ 	/*
  	 * No more output to be done. Close the temp file and replace the old
  	 * pgstat.stat with it.  The ferror() check replaces testing for error
  	 * after each individual fputc or fwrite above.
*************** pgstat_read_statsfile(Oid onlydb, bool p
*** 3608,3613 ****
--- 3723,3730 ----
  	PgStat_StatTabEntry tabbuf;
  	PgStat_StatFuncEntry funcbuf;
  	PgStat_StatFuncEntry *funcentry;
+ 	PgStat_RecoveryStats recoverybuf;
+ 	PgStat_RecoveryStats *recoveryentry;
  	HASHCTL		hash_ctl;
  	HTAB	   *dbhash;
  	HTAB	   *tabhash = NULL;
*************** pgstat_read_statsfile(Oid onlydb, bool p
*** 3634,3639 ****
--- 3751,3771 ----
  						 HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
  
  	/*
+ 	 * And the recovery hashtable
+ 	 */
+ 	if (!pgStatRecoveryHash)
+ 	{
+ 		hash_ctl.keysize = sizeof(Oid);
+ 		hash_ctl.entrysize = sizeof(PgStat_RecoveryStats);
+ 		hash_ctl.hash = oid_hash;
+ 		hash_ctl.hcxt = pgStatLocalContext;
+ 		pgStatRecoveryHash = hash_create("Per-cluster recovery",
+ 								   PGSTAT_RECOVERY_HASH_SIZE,
+ 								   &hash_ctl,
+ 							       HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
+ 	}
+ 
+ 	/*
  	 * Clear out global statistics so they start from zero in case we can't
  	 * load an existing statsfile.
  	 */
*************** pgstat_read_statsfile(Oid onlydb, bool p
*** 3842,3847 ****
--- 3974,4011 ----
  				break;
  
  				/*
+ 				 * 'R'	A PgStat_RecoveryStats follows. This describes
+ 				 *      recovery activity
+ 				 */
+ 			case 'R':
+ 				/* if the table is already loaded avoid this */
+ 				if (!recoveryHashLoaded)
+ 				{
+ 					if (fread(&recoverybuf, 1, sizeof(PgStat_RecoveryStats),
+ 							  fpin) != sizeof(PgStat_RecoveryStats))
+ 					{
+ 						ereport(pgStatRunningInCollector ? LOG : WARNING,
+ 								(errmsg("corrupted statistics file \"%s\"",
+ 										statfile)));
+ 						goto done;
+ 					}
+ 
+ 					recoveryentry = (PgStat_RecoveryStats *) hash_search(pgStatRecoveryHash,
+ 													(void *) &(recoverybuf.rectypeid),
+ 															 HASH_ENTER, &found);
+ 	
+ 					if (found)
+ 					{
+ 						ereport(pgStatRunningInCollector ? LOG : WARNING,
+ 								(errmsg("corrupted statistics file \"%s\"",
+ 										statfile)));
+ 						goto done;
+ 					}
+ 					memcpy(recoveryentry, &recoverybuf, sizeof(recoverybuf));
+ 				}
+ 				break;
+ 
+ 				/*
  				 * 'E'	The EOF marker of a complete stats file.
  				 */
  			case 'E':
*************** pgstat_read_statsfile(Oid onlydb, bool p
*** 3858,3863 ****
--- 4022,4029 ----
  done:
  	FreeFile(fpin);
  
+ 	recoveryHashLoaded = true;
+ 
  	if (permanent)
  		unlink(PGSTAT_STAT_PERMANENT_FILENAME);
  
*************** pgstat_clear_snapshot(void)
*** 4030,4035 ****
--- 4196,4203 ----
  	/* Reset variables */
  	pgStatLocalContext = NULL;
  	pgStatDBHash = NULL;
+ 	recoveryHashLoaded = false;
+ 	pgStatRecoveryHash = NULL;
  	localBackendStatusTable = NULL;
  	localNumBackends = 0;
  }
*************** pgstat_recv_recoveryconflict(PgStat_MsgR
*** 4492,4497 ****
--- 4660,4725 ----
  }
  
  /* ----------
+  * pgstat_recv_recoverystat() -
+  *
+  *	Process as RECOVERYSTAT message.
+  * ----------
+  */
+ static void
+ pgstat_recv_recoverystat(PgStat_MsgRecoverystat *msg, int len)
+ {
+ 	int rectypeid = (msg->rmid * XLR_INFO_MASK) + msg->info;
+ 	PgStat_RecoveryStats *recoveryentry;
+ 	bool		found;
+ 
+ 	recoveryentry = (PgStat_RecoveryStats *) hash_search(pgStatRecoveryHash,
+ 														 (void *) &(rectypeid),
+ 														 HASH_ENTER, &found);
+ 
+ 	if (!found)
+ 	{
+ 		/*
+ 		 * If it's a new recovery entry, initialize counters to the values
+ 		 * we just got.
+ 		 */
+ 		recoveryentry->rectypeid = rectypeid;
+ 		recoveryentry->rmid = msg->rmid;
+ 		recoveryentry->info = msg->info;
+ 		recoveryentry->n_records = 1; 
+ 		recoveryentry->tot_record_size = msg->record_size; 
+ 		recoveryentry->max_record_size = msg->record_size; 
+ 		recoveryentry->min_record_size = msg->record_size; 
+ 		recoveryentry->tot_redo_time = msg->redo_time;
+ 		recoveryentry->max_redo_time = msg->redo_time;
+ 		recoveryentry->min_redo_time = msg->redo_time;
+ 		recoveryentry->n_bkp_blocks = msg->n_bkp_blocks; 
+ 	}
+ 	else
+ 	{
+ 		/*
+ 		 * Otherwise add the values to the existing entry.
+ 		 */
+ 		recoveryentry->n_records += 1; 
+ 		recoveryentry->n_bkp_blocks += msg->n_bkp_blocks; 
+ 
+ 		recoveryentry->tot_record_size += msg->record_size; 
+ 		if (msg->record_size > recoveryentry->max_record_size)
+ 			recoveryentry->max_record_size = msg->record_size; 
+ 		if (msg->record_size < recoveryentry->min_record_size)
+ 			recoveryentry->min_record_size = msg->record_size; 
+ 
+ 		recoveryentry->tot_redo_time += msg->redo_time;
+ 		if (msg->redo_time > recoveryentry->max_redo_time)
+ 			recoveryentry->max_redo_time = msg->redo_time;
+ 		if (msg->redo_time < recoveryentry->min_redo_time)
+ 			recoveryentry->min_redo_time = msg->redo_time;
+ 	}
+ 
+ 	/* common values */
+ 	recoveryentry->last_process_time = msg->process_time;
+ }
+ 
+ /* ----------
   * pgstat_recv_deadlock() -
   *
   *	Process a DEADLOCK message.
diff --git a/src/backend/storage/ipc/standby.c b/src/backend/storage/ipc/standby.c
index dc6833b..9898637 100644
*** a/src/backend/storage/ipc/standby.c
--- b/src/backend/storage/ipc/standby.c
*************** standby_desc(StringInfo buf, uint8 xl_in
*** 798,803 ****
--- 798,821 ----
  		appendStringInfo(buf, "UNKNOWN");
  }
  
+ void
+ standby_short_desc(char buf[50], uint8 xl_info)
+ {
+ 	uint8		info = xl_info & ~XLR_INFO_MASK;
+ 
+ 	switch (info)
+ 	{
+ 		case XLOG_STANDBY_LOCK:
+ 			sprintf(buf, "Standby AccessExclusive locks");
+ 			break;
+ 		case XLOG_RUNNING_XACTS:
+ 			sprintf(buf, "Standby running xacts");
+ 			break;
+ 		default:
+ 			sprintf(buf, "UNKNOWN STANDBY INFO");
+ 	}
+ }
+ 
  /*
   * Log details of the current snapshot to WAL. This allows the snapshot state
   * to be reconstructed on the standby.
diff --git a/src/backend/utils/cache/relmapper.c b/src/backend/utils/cache/relmapper.c
index 306832e..0f06421 100644
*** a/src/backend/utils/cache/relmapper.c
--- b/src/backend/utils/cache/relmapper.c
*************** relmap_desc(StringInfo buf, uint8 xl_inf
*** 909,911 ****
--- 909,926 ----
  	else
  		appendStringInfo(buf, "UNKNOWN");
  }
+ 
+ void
+ relmap_short_desc(char buf[50], uint8 xl_info)
+ {
+ 	uint8		info = xl_info & ~XLR_INFO_MASK;
+ 
+ 	switch (info)
+ 	{
+ 		case XLOG_RELMAP_UPDATE:
+ 			sprintf(buf, "update relmap");
+ 			break;
+ 		default:
+ 			sprintf(buf, "UNKNOWN RELMAP INFO");
+ 	}
+ }
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 7df5292..1432987 100644
*** a/src/backend/utils/misc/guc.c
--- b/src/backend/utils/misc/guc.c
*************** static struct config_bool ConfigureNames
*** 1017,1022 ****
--- 1017,1031 ----
  		true,
  		NULL, NULL, NULL
  	},
+ 	{
+ 		{"track_recovery", PGC_POSTMASTER, STATS_COLLECTOR,
+ 			gettext_noop("Collects statistics on recovery activity."),
+ 			NULL
+ 		},
+ 		&pgstat_track_recovery,
+ 		false,
+ 		NULL, NULL, NULL
+ 	},
  
  	{
  		{"update_process_title", PGC_SUSET, STATS_COLLECTOR,
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 400c52b..959a845 100644
*** a/src/backend/utils/misc/postgresql.conf.sample
--- b/src/backend/utils/misc/postgresql.conf.sample
***************
*** 420,425 ****
--- 420,426 ----
  
  #track_activities = on
  #track_counts = on
+ #track_recovery = off
  #track_functions = none			# none, pl, all
  #track_activity_query_size = 1024 	# (change requires restart)
  #update_process_title = on
diff --git a/src/include/access/clog.h b/src/include/access/clog.h
index bed3b8c..50af239 100644
*** a/src/include/access/clog.h
--- b/src/include/access/clog.h
*************** extern void TruncateCLOG(TransactionId o
*** 49,53 ****
--- 49,54 ----
  
  extern void clog_redo(XLogRecPtr lsn, XLogRecord *record);
  extern void clog_desc(StringInfo buf, uint8 xl_info, char *rec);
+ extern void clog_short_desc(char buf[50], uint8 xl_info);
  
  #endif   /* CLOG_H */
diff --git a/src/include/access/gin.h b/src/include/access/gin.h
index 36e490a..76be479 100644
*** a/src/include/access/gin.h
--- b/src/include/access/gin.h
*************** extern void ginUpdateStats(Relation inde
*** 56,61 ****
--- 56,62 ----
  /* ginxlog.c */
  extern void gin_redo(XLogRecPtr lsn, XLogRecord *record);
  extern void gin_desc(StringInfo buf, uint8 xl_info, char *rec);
+ extern void gin_short_desc(char buf[50], uint8 xl_info);
  extern void gin_xlog_startup(void);
  extern void gin_xlog_cleanup(void);
  extern bool gin_safe_restartpoint(void);
diff --git a/src/include/access/gist_private.h b/src/include/access/gist_private.h
index 0d6b625..43fc364 100644
*** a/src/include/access/gist_private.h
--- b/src/include/access/gist_private.h
*************** extern SplitedPageLayout *gistSplit(Rela
*** 458,463 ****
--- 458,464 ----
  /* gistxlog.c */
  extern void gist_redo(XLogRecPtr lsn, XLogRecord *record);
  extern void gist_desc(StringInfo buf, uint8 xl_info, char *rec);
+ extern void gist_short_desc(char buf[50], uint8 xl_info);
  extern void gist_xlog_startup(void);
  extern void gist_xlog_cleanup(void);
  
diff --git a/src/include/access/hash.h b/src/include/access/hash.h
index a3d0f98..6fe74fd 100644
*** a/src/include/access/hash.h
--- b/src/include/access/hash.h
*************** extern OffsetNumber _hash_binsearch_last
*** 356,360 ****
--- 356,361 ----
  /* hash.c */
  extern void hash_redo(XLogRecPtr lsn, XLogRecord *record);
  extern void hash_desc(StringInfo buf, uint8 xl_info, char *rec);
+ extern void hash_short_desc(char buf[50], uint8 xl_info);
  
  #endif   /* HASH_H */
diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h
index fa38803..9f27fde 100644
*** a/src/include/access/heapam.h
--- b/src/include/access/heapam.h
*************** extern void heap_sync(Relation relation)
*** 128,135 ****
--- 128,137 ----
  
  extern void heap_redo(XLogRecPtr lsn, XLogRecord *rptr);
  extern void heap_desc(StringInfo buf, uint8 xl_info, char *rec);
+ extern void heap_short_desc(char buf[50], uint8 xl_info);
  extern void heap2_redo(XLogRecPtr lsn, XLogRecord *rptr);
  extern void heap2_desc(StringInfo buf, uint8 xl_info, char *rec);
+ extern void heap2_short_desc(char buf[50], uint8 xl_info);
  
  extern XLogRecPtr log_heap_cleanup_info(RelFileNode rnode,
  					  TransactionId latestRemovedXid);
diff --git a/src/include/access/multixact.h b/src/include/access/multixact.h
index e20573d..329ed8b 100644
*** a/src/include/access/multixact.h
--- b/src/include/access/multixact.h
*************** extern void multixact_twophase_postabort
*** 78,82 ****
--- 78,83 ----
  
  extern void multixact_redo(XLogRecPtr lsn, XLogRecord *record);
  extern void multixact_desc(StringInfo buf, uint8 xl_info, char *rec);
+ extern void multixact_short_desc(char buf[50], uint8 xl_info);
  
  #endif   /* MULTIXACT_H */
diff --git a/src/include/access/nbtree.h b/src/include/access/nbtree.h
index 041733c..c5c80c9 100644
*** a/src/include/access/nbtree.h
--- b/src/include/access/nbtree.h
*************** extern void _bt_leafbuild(BTSpool *btspo
*** 691,696 ****
--- 691,697 ----
   */
  extern void btree_redo(XLogRecPtr lsn, XLogRecord *record);
  extern void btree_desc(StringInfo buf, uint8 xl_info, char *rec);
+ extern void btree_short_desc(char buf[50], uint8 xl_info);
  extern void btree_xlog_startup(void);
  extern void btree_xlog_cleanup(void);
  extern bool btree_safe_restartpoint(void);
diff --git a/src/include/access/spgist.h b/src/include/access/spgist.h
index cd6de2c..ac528f6 100644
*** a/src/include/access/spgist.h
--- b/src/include/access/spgist.h
*************** extern Datum spgvacuumcleanup(PG_FUNCTIO
*** 198,203 ****
--- 198,204 ----
  /* spgxlog.c */
  extern void spg_redo(XLogRecPtr lsn, XLogRecord *record);
  extern void spg_desc(StringInfo buf, uint8 xl_info, char *rec);
+ extern void spg_short_desc(char buf[50], uint8 xl_info);
  extern void spg_xlog_startup(void);
  extern void spg_xlog_cleanup(void);
  
diff --git a/src/include/access/xact.h b/src/include/access/xact.h
index 20e344e..214bf7a 100644
*** a/src/include/access/xact.h
--- b/src/include/access/xact.h
*************** extern int	xactGetCommittedChildren(Tran
*** 249,253 ****
--- 249,254 ----
  
  extern void xact_redo(XLogRecPtr lsn, XLogRecord *record);
  extern void xact_desc(StringInfo buf, uint8 xl_info, char *rec);
+ extern void xact_short_desc(char buf[50], uint8 xl_info);
  
  #endif   /* XACT_H */
diff --git a/src/include/access/xlog.h b/src/include/access/xlog.h
index f8aecef..8fcf85c 100644
*** a/src/include/access/xlog.h
--- b/src/include/access/xlog.h
*************** extern void RestoreBkpBlocks(XLogRecPtr
*** 280,285 ****
--- 280,286 ----
  
  extern void xlog_redo(XLogRecPtr lsn, XLogRecord *record);
  extern void xlog_desc(StringInfo buf, uint8 xl_info, char *rec);
+ extern void xlog_short_desc(char buf[50], uint8 xl_info);
  
  extern void issue_xlog_fsync(int fd, uint32 log, uint32 seg);
  
diff --git a/src/include/access/xlog_internal.h b/src/include/access/xlog_internal.h
index b81c156..b2dce00 100644
*** a/src/include/access/xlog_internal.h
--- b/src/include/access/xlog_internal.h
*************** typedef struct RmgrData
*** 250,255 ****
--- 250,256 ----
  	const char *rm_name;
  	void		(*rm_redo) (XLogRecPtr lsn, XLogRecord *rptr);
  	void		(*rm_desc) (StringInfo buf, uint8 xl_info, char *rec);
+ 	void		(*rm_short_desc) (char buf[], uint8 xl_info);
  	void		(*rm_startup) (void);
  	void		(*rm_cleanup) (void);
  	bool		(*rm_safe_restartpoint) (void);
*************** extern Datum pg_is_in_recovery(PG_FUNCTI
*** 281,285 ****
--- 282,287 ----
  extern Datum pg_xlog_replay_pause(PG_FUNCTION_ARGS);
  extern Datum pg_xlog_replay_resume(PG_FUNCTION_ARGS);
  extern Datum pg_is_xlog_replay_paused(PG_FUNCTION_ARGS);
+ extern Datum pg_stat_get_recovery_activity(PG_FUNCTION_ARGS);
  
  #endif   /* XLOG_INTERNAL_H */
diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h
index ba4f5b6..0168526 100644
*** a/src/include/catalog/pg_proc.h
--- b/src/include/catalog/pg_proc.h
*************** DATA(insert OID = 2022 (  pg_stat_get_ac
*** 2580,2585 ****
--- 2580,2587 ----
  DESCR("statistics: information about currently active backends");
  DATA(insert OID = 3099 (  pg_stat_get_wal_senders	PGNSP PGUID 12 1 10 0 0 f f f f t s 0 0 2249 "" "{23,25,25,25,25,25,23,25}" "{o,o,o,o,o,o,o,o}" "{pid,state,sent_location,write_location,flush_location,replay_location,sync_priority,sync_state}" _null_ pg_stat_get_wal_senders _null_ _null_ _null_ ));
  DESCR("statistics: information about currently active replication");
+ DATA(insert OID = 3153 (  pg_stat_get_recovery_activity			PGNSP PGUID 12 1 100 0 0 f f f f t s 0 0 2249 "" "{25,25,23,23,23,23,23,701,701,701,1184}" "{o,o,o,o,o,o,o,o,o,o,o}" "{rmgr,wal_record_type,n_records,n_bkp_blocks,tot_record_size,max_record_size,min_record_size,tot_redo_time,max_redo_time,min_redo_time,last_process_time}" _null_ pg_stat_get_recovery_activity _null_ _null_ _null_ ));
+ DESCR("statistics: information about recovery activity");
  DATA(insert OID = 2026 (  pg_backend_pid				PGNSP PGUID 12 1 0 0 0 f f f t f s 0 0 23 "" _null_ _null_ _null_ _null_ pg_backend_pid _null_ _null_ _null_ ));
  DESCR("statistics: current backend PID");
  DATA(insert OID = 1937 (  pg_stat_get_backend_pid		PGNSP PGUID 12 1 0 0 0 f f f t f s 1 0 23 "23" _null_ _null_ _null_ _null_ pg_stat_get_backend_pid _null_ _null_ _null_ ));
diff --git a/src/include/catalog/storage.h b/src/include/catalog/storage.h
index d5103a8..62cdc16 100644
*** a/src/include/catalog/storage.h
--- b/src/include/catalog/storage.h
*************** extern void log_smgrcreate(RelFileNode *
*** 38,42 ****
--- 38,43 ----
  
  extern void smgr_redo(XLogRecPtr lsn, XLogRecord *record);
  extern void smgr_desc(StringInfo buf, uint8 xl_info, char *rec);
+ extern void smgr_short_desc(char buf[50], uint8 xl_info);
  
  #endif   /* STORAGE_H */
diff --git a/src/include/commands/dbcommands.h b/src/include/commands/dbcommands.h
index 41ca8ff..13189cd 100644
*** a/src/include/commands/dbcommands.h
--- b/src/include/commands/dbcommands.h
*************** extern char *get_database_name(Oid dbid)
*** 64,69 ****
--- 64,70 ----
  
  extern void dbase_redo(XLogRecPtr lsn, XLogRecord *rptr);
  extern void dbase_desc(StringInfo buf, uint8 xl_info, char *rec);
+ extern void dbase_short_desc(char buf[50], uint8 xl_info);
  
  extern void check_encoding_locale_matches(int encoding, const char *collate, const char *ctype);
  
diff --git a/src/include/commands/sequence.h b/src/include/commands/sequence.h
index 2cdf86f..ffc6935 100644
*** a/src/include/commands/sequence.h
--- b/src/include/commands/sequence.h
*************** extern void ResetSequence(Oid seq_relid)
*** 77,81 ****
--- 77,82 ----
  
  extern void seq_redo(XLogRecPtr lsn, XLogRecord *rptr);
  extern void seq_desc(StringInfo buf, uint8 xl_info, char *rec);
+ extern void seq_short_desc(char buf[50], uint8 xl_info);
  
  #endif   /* SEQUENCE_H */
diff --git a/src/include/commands/tablespace.h b/src/include/commands/tablespace.h
index fe8b6a5..c3ccd0e 100644
*** a/src/include/commands/tablespace.h
--- b/src/include/commands/tablespace.h
*************** extern bool directory_is_empty(const cha
*** 58,62 ****
--- 58,63 ----
  
  extern void tblspc_redo(XLogRecPtr lsn, XLogRecord *rptr);
  extern void tblspc_desc(StringInfo buf, uint8 xl_info, char *rec);
+ extern void tblspc_short_desc(char buf[50], uint8 xl_info);
  
  #endif   /* TABLESPACE_H */
diff --git a/src/include/pgstat.h b/src/include/pgstat.h
index 1281bd8..47d7b0e 100644
*** a/src/include/pgstat.h
--- b/src/include/pgstat.h
***************
*** 11,16 ****
--- 11,18 ----
  #ifndef PGSTAT_H
  #define PGSTAT_H
  
+ #include "access/rmgr.h"
+ #include "access/xlog.h"
  #include "datatype/timestamp.h"
  #include "fmgr.h"
  #include "libpq/pqcomm.h"
***************
*** 22,30 ****
  /* Values for track_functions GUC variable --- order is significant! */
  typedef enum TrackFunctionsLevel
  {
! 	TRACK_FUNC_OFF,
! 	TRACK_FUNC_PL,
! 	TRACK_FUNC_ALL
  }	TrackFunctionsLevel;
  
  /* ----------
--- 24,32 ----
  /* Values for track_functions GUC variable --- order is significant! */
  typedef enum TrackFunctionsLevel
  {
! TRACK_FUNC_OFF,
! TRACK_FUNC_PL,
! TRACK_FUNC_ALL
  }	TrackFunctionsLevel;
  
  /* ----------
*************** typedef enum StatMsgType
*** 48,53 ****
--- 50,56 ----
  	PGSTAT_MTYPE_FUNCSTAT,
  	PGSTAT_MTYPE_FUNCPURGE,
  	PGSTAT_MTYPE_RECOVERYCONFLICT,
+ 	PGSTAT_MTYPE_RECOVERYSTAT,
  	PGSTAT_MTYPE_TEMPFILE,
  	PGSTAT_MTYPE_DEADLOCK
  } StatMsgType;
*************** typedef struct PgStat_MsgTempFile
*** 391,396 ****
--- 394,442 ----
  } PgStat_MsgTempFile;
  
  /* ----------
+  * PgStat_MsgRecoverystat	Sent by the backend upon recovery conflict
+  * ----------
+  */
+ 
+ #define PGSTAT_NUM_RECOVERYSTAT_ENTRIES 600 
+ 
+ typedef struct PgStat_MsgRecoverystat
+ {
+ 	PgStat_MsgHdr m_hdr;
+ 
+ 	RmgrId			rmid;
+ 	uint8			info;
+ 	PgStat_Counter	n_bkp_blocks;
+ 	int				record_size;
+ 	TimestampTz		process_time;
+ 	double			redo_time;
+ } PgStat_MsgRecoverystat;
+ 
+ /*
+  * Recovery statistics kept in both startup process and stats collector
+  */
+ typedef struct PgStat_RecoveryStats
+ {
+ 	/* 
+ 	 * rectypeid contains the full identification of the row which is 
+ 	 * just calculated by (rmid * XLR_INFO_MASK) + info.
+ 	 * it's kept apart just because it's a convenient key for the hash table
+      */
+ 	int				rectypeid;
+ 	RmgrId			rmid;
+ 	uint8			info;
+ 	PgStat_Counter	n_records;
+ 	PgStat_Counter	n_bkp_blocks;
+ 	int				tot_record_size;
+ 	int				max_record_size;
+ 	int				min_record_size;
+ 	double			tot_redo_time;
+ 	double			max_redo_time;
+ 	double			min_redo_time;
+ 	TimestampTz		last_process_time;
+ } PgStat_RecoveryStats;
+ 
+ /* ----------
   * PgStat_FunctionCounts	The actual per-function counts kept by a backend
   *
   * This struct should contain only actual event counters, because we memcmp
*************** typedef union PgStat_Msg
*** 497,502 ****
--- 543,549 ----
  	PgStat_MsgFuncstat msg_funcstat;
  	PgStat_MsgFuncpurge msg_funcpurge;
  	PgStat_MsgRecoveryConflict msg_recoveryconflict;
+ 	PgStat_MsgRecoverystat msg_recoverystat;
  	PgStat_MsgDeadlock msg_deadlock;
  } PgStat_Msg;
  
*************** typedef struct PgStat_FunctionCallUsage
*** 711,716 ****
--- 758,764 ----
   */
  extern bool pgstat_track_activities;
  extern bool pgstat_track_counts;
+ extern bool pgstat_track_recovery;
  extern int	pgstat_track_functions;
  extern PGDLLIMPORT int pgstat_track_activity_query_size;
  extern char *pgstat_stat_tmpname;
*************** extern void pgstat_report_analyze(Relati
*** 760,765 ****
--- 808,815 ----
  					  PgStat_Counter livetuples, PgStat_Counter deadtuples);
  
  extern void pgstat_report_recovery_conflict(int reason);
+ extern void pgstat_report_recovery_stats(XLogRecord *record, TimestampTz before_ts, TimestampTz after_ts);
+ 
  extern void pgstat_report_deadlock(void);
  
  extern void pgstat_initialize(void);
*************** extern PgBackendStatus *pgstat_fetch_sta
*** 851,855 ****
--- 901,906 ----
  extern PgStat_StatFuncEntry *pgstat_fetch_stat_funcentry(Oid funcid);
  extern int	pgstat_fetch_stat_numbackends(void);
  extern PgStat_GlobalStats *pgstat_fetch_global(void);
+ extern int pgstat_fetch_recoverystats(PgStat_RecoveryStats *recoveryStats);
  
  #endif   /* PGSTAT_H */
diff --git a/src/include/storage/standby.h b/src/include/storage/standby.h
index 1027bbc..b6d1b60 100644
*** a/src/include/storage/standby.h
--- b/src/include/storage/standby.h
*************** typedef struct xl_running_xacts
*** 82,87 ****
--- 82,88 ----
  /* Recovery handlers for the Standby Rmgr (RM_STANDBY_ID) */
  extern void standby_redo(XLogRecPtr lsn, XLogRecord *record);
  extern void standby_desc(StringInfo buf, uint8 xl_info, char *rec);
+ extern void standby_short_desc(char buf[50], uint8 xl_info);
  
  /*
   * Declarations for GetRunningTransactionData(). Similar to Snapshots, but
diff --git a/src/include/utils/relmapper.h b/src/include/utils/relmapper.h
index 111a05c..d198a1f 100644
*** a/src/include/utils/relmapper.h
--- b/src/include/utils/relmapper.h
*************** extern void RelationMapInitializePhase3(
*** 58,62 ****
--- 58,63 ----
  
  extern void relmap_redo(XLogRecPtr lsn, XLogRecord *record);
  extern void relmap_desc(StringInfo buf, uint8 xl_info, char *rec);
+ extern void relmap_short_desc(char buf[50], uint8 xl_info);
  
  #endif   /* RELMAPPER_H */
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index f67b8dc..a583a37 100644
*** a/src/test/regress/expected/rules.out
--- b/src/test/regress/expected/rules.out
*************** SELECT viewname, definition FROM pg_view
*** 1298,1303 ****
--- 1298,1304 ----
   pg_stat_bgwriter                | SELECT pg_stat_get_bgwriter_timed_checkpoints() AS checkpoints_timed, pg_stat_get_bgwriter_requested_checkpoints() AS checkpoints_req, pg_stat_get_bgwriter_buf_written_checkpoints() AS buffers_checkpoint, pg_stat_get_bgwriter_buf_written_clean() AS buffers_clean, pg_stat_get_bgwriter_maxwritten_clean() AS maxwritten_clean, pg_stat_get_buf_written_backend() AS buffers_backend, pg_stat_get_buf_fsync_backend() AS buffers_backend_fsync, pg_stat_get_buf_alloc() AS buffers_alloc, pg_stat_get_bgwriter_stat_reset_time() AS stats_reset;
   pg_stat_database                | SELECT d.oid AS datid, d.datname, pg_stat_get_db_numbackends(d.oid) AS numbackends, pg_stat_get_db_xact_commit(d.oid) AS xact_commit, pg_stat_get_db_xact_rollback(d.oid) AS xact_rollback, (pg_stat_get_db_blocks_fetched(d.oid) - pg_stat_get_db_blocks_hit(d.oid)) AS blks_read, pg_stat_get_db_blocks_hit(d.oid) AS blks_hit, pg_stat_get_db_tuples_returned(d.oid) AS tup_returned, pg_stat_get_db_tuples_fetched(d.oid) AS tup_fetched, pg_stat_get_db_tuples_inserted(d.oid) AS tup_inserted, pg_stat_get_db_tuples_updated(d.oid) AS tup_updated, pg_stat_get_db_tuples_deleted(d.oid) AS tup_deleted, pg_stat_get_db_conflict_all(d.oid) AS conflicts, pg_stat_get_db_temp_files(d.oid) AS temp_files, pg_stat_get_db_temp_bytes(d.oid) AS temp_bytes, pg_stat_get_db_deadlocks(d.oid) AS deadlocks, pg_stat_get_db_stat_reset_time(d.oid) AS stats_reset FROM pg_database d;
   pg_stat_database_conflicts      | SELECT d.oid AS datid, d.datname, pg_stat_get_db_conflict_tablespace(d.oid) AS confl_tablespace, pg_stat_get_db_conflict_lock(d.oid) AS confl_lock, pg_stat_get_db_conflict_snapshot(d.oid) AS confl_snapshot, pg_stat_get_db_conflict_bufferpin(d.oid) AS confl_bufferpin, pg_stat_get_db_conflict_startup_deadlock(d.oid) AS confl_deadlock FROM pg_database d;
+  pg_stat_recovery                | SELECT r.rmgr, r.wal_record_type, r.n_records, r.n_bkp_blocks, r.tot_record_size, r.max_record_size, r.min_record_size, r.tot_redo_time, r.max_redo_time, r.min_redo_time, r.last_process_time FROM pg_stat_get_recovery_activity() r(rmgr, wal_record_type, n_records, n_bkp_blocks, tot_record_size, max_record_size, min_record_size, tot_redo_time, max_redo_time, min_redo_time, last_process_time);
   pg_stat_replication             | SELECT s.pid, s.usesysid, u.rolname AS usename, s.application_name, s.client_addr, s.client_hostname, s.client_port, s.backend_start, w.state, w.sent_location, w.write_location, w.flush_location, w.replay_location, w.sync_priority, w.sync_state FROM pg_stat_get_activity(NULL::integer) s(datid, pid, usesysid, application_name, state, query, waiting, xact_start, query_start, backend_start, state_change, client_addr, client_hostname, client_port), pg_authid u, pg_stat_get_wal_senders() w(pid, state, sent_location, write_location, flush_location, replay_location, sync_priority, sync_state) WHERE ((s.usesysid = u.oid) AND (s.pid = w.pid));
   pg_stat_sys_indexes             | SELECT pg_stat_all_indexes.relid, pg_stat_all_indexes.indexrelid, pg_stat_all_indexes.schemaname, pg_stat_all_indexes.relname, pg_stat_all_indexes.indexrelname, pg_stat_all_indexes.idx_scan, pg_stat_all_indexes.idx_tup_read, pg_stat_all_indexes.idx_tup_fetch FROM pg_stat_all_indexes WHERE ((pg_stat_all_indexes.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_indexes.schemaname ~ '^pg_toast'::text));
   pg_stat_sys_tables              | SELECT pg_stat_all_tables.relid, pg_stat_all_tables.schemaname, pg_stat_all_tables.relname, pg_stat_all_tables.seq_scan, pg_stat_all_tables.seq_tup_read, pg_stat_all_tables.idx_scan, pg_stat_all_tables.idx_tup_fetch, pg_stat_all_tables.n_tup_ins, pg_stat_all_tables.n_tup_upd, pg_stat_all_tables.n_tup_del, pg_stat_all_tables.n_tup_hot_upd, pg_stat_all_tables.n_live_tup, pg_stat_all_tables.n_dead_tup, pg_stat_all_tables.last_vacuum, pg_stat_all_tables.last_autovacuum, pg_stat_all_tables.last_analyze, pg_stat_all_tables.last_autoanalyze, pg_stat_all_tables.vacuum_count, pg_stat_all_tables.autovacuum_count, pg_stat_all_tables.analyze_count, pg_stat_all_tables.autoanalyze_count FROM pg_stat_all_tables WHERE ((pg_stat_all_tables.schemaname = ANY (ARRAY['pg_catalog'::name, 'information_schema'::name])) OR (pg_stat_all_tables.schemaname ~ '^pg_toast'::text));
*************** SELECT viewname, definition FROM pg_view
*** 1338,1344 ****
   shoelace_obsolete               | SELECT shoelace.sl_name, shoelace.sl_avail, shoelace.sl_color, shoelace.sl_len, shoelace.sl_unit, shoelace.sl_len_cm FROM shoelace WHERE (NOT (EXISTS (SELECT shoe.shoename FROM shoe WHERE (shoe.slcolor = shoelace.sl_color))));
   street                          | SELECT r.name, r.thepath, c.cname FROM ONLY road r, real_city c WHERE (c.outline ## r.thepath);
   toyemp                          | SELECT emp.name, emp.age, emp.location, (12 * emp.salary) AS annualsal FROM emp;
! (60 rows)
  
  SELECT tablename, rulename, definition FROM pg_rules
  	ORDER BY tablename, rulename;
--- 1339,1345 ----
   shoelace_obsolete               | SELECT shoelace.sl_name, shoelace.sl_avail, shoelace.sl_color, shoelace.sl_len, shoelace.sl_unit, shoelace.sl_len_cm FROM shoelace WHERE (NOT (EXISTS (SELECT shoe.shoename FROM shoe WHERE (shoe.slcolor = shoelace.sl_color))));
   street                          | SELECT r.name, r.thepath, c.cname FROM ONLY road r, real_city c WHERE (c.outline ## r.thepath);
   toyemp                          | SELECT emp.name, emp.age, emp.location, (12 * emp.salary) AS annualsal FROM emp;
! (61 rows)
  
  SELECT tablename, rulename, definition FROM pg_rules
  	ORDER BY tablename, rulename;
diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list
index 7480ec4..9631674 100644
*** a/src/tools/pgindent/typedefs.list
--- b/src/tools/pgindent/typedefs.list
*************** PgStat_MsgFuncstat
*** 1200,1211 ****
--- 1200,1213 ----
  PgStat_MsgHdr
  PgStat_MsgInquiry
  PgStat_MsgRecoveryConflict
+ PgStat_MsgRecoverystat
  PgStat_MsgResetcounter
  PgStat_MsgResetsharedcounter
  PgStat_MsgResetsinglecounter
  PgStat_MsgTabpurge
  PgStat_MsgTabstat
  PgStat_MsgVacuum
+ PgStat_RecoveryStats
  PgStat_Shared_Reset_Target
  PgStat_Single_Reset_Type
  PgStat_StatDBEntry
#4Fujii Masao
masao.fujii@gmail.com
In reply to: Jaime Casanova (#3)
Re: pg_stats_recovery view

On Fri, Jan 27, 2012 at 6:01 AM, Jaime Casanova <jaime@2ndquadrant.com> wrote:

On Thu, Jan 26, 2012 at 4:03 AM, Bernd Helmle <mailings@oopsware.de> wrote:

--On 15. Januar 2012 02:50:00 -0500 Jaime Casanova <jaime@2ndquadrant.com>
wrote:

Attached is a patch thats implements a pg_stat_recovery view that
keeps counters about processed wal records. I just notice that it
still lacks documentation but i will add it during the week.

Hi Jaime,

do you have an updated patch? The current v1 patch doesn't apply cleanly
anymore, and before i go and rebase the patch i thought i'm asking...

here's the patch rebased to this morning's HEAD

Before reviewing the patch, I'd like to know: what's the purpose of this view?
It's only debug purpose? ISTM that most users don't care about this view at all.

Regards,

--
Fujii Masao
NIPPON TELEGRAPH AND TELEPHONE CORPORATION
NTT Open Source Software Center

#5Jaime Casanova
jaime@2ndquadrant.com
In reply to: Fujii Masao (#4)
Re: pg_stats_recovery view

On Wed, Feb 1, 2012 at 9:18 PM, Fujii Masao <masao.fujii@gmail.com> wrote:

--On 15. Januar 2012 02:50:00 -0500 Jaime Casanova <jaime@2ndquadrant.com>
wrote:

Attached is a patch thats implements a pg_stat_recovery view that
keeps counters about processed wal records. I just notice that it
still lacks documentation but i will add it during the week.

Before reviewing the patch, I'd like to know: what's the purpose of this view?
It's only debug purpose? ISTM that most users don't care about this view at all.

yeah! you're right. most users won't care about it... did i tell that
i added a track_recovery GUC so only users that wanted pay for it? i
probably did not tell that :D

--
Jaime Casanova         www.2ndQuadrant.com
Professional PostgreSQL: Soporte 24x7 y capacitación

#6Magnus Hagander
magnus@hagander.net
In reply to: Jaime Casanova (#5)
Re: pg_stats_recovery view

On Thu, Feb 2, 2012 at 08:26, Jaime Casanova <jaime@2ndquadrant.com> wrote:

On Wed, Feb 1, 2012 at 9:18 PM, Fujii Masao <masao.fujii@gmail.com> wrote:

--On 15. Januar 2012 02:50:00 -0500 Jaime Casanova <jaime@2ndquadrant.com>
wrote:

Attached is a patch thats implements a pg_stat_recovery view that
keeps counters about processed wal records. I just notice that it
still lacks documentation but i will add it during the week.

Before reviewing the patch, I'd like to know: what's the purpose of this view?
It's only debug purpose? ISTM that most users don't care about this view at all.

yeah! you're right. most users won't care about it... did i tell that
i added a track_recovery GUC so only users that wanted pay for it? i
probably did not tell that :D

I haven't looked through the code in detail, but one direct comment:
do we really need/want to send this through the stats collector? It
will only ever have one sender - perhaps we should just either store
it in shared memory or store it locally and only send it on demand?

(apologies if it already does the on-demand thing, I only spent about
30 seconds looking for it and noticed it did go through the stats
collector...)

--
 Magnus Hagander
 Me: http://www.hagander.net/
 Work: http://www.redpill-linpro.com/

#7Fujii Masao
masao.fujii@gmail.com
In reply to: Jaime Casanova (#5)
Re: pg_stats_recovery view

On Thu, Feb 2, 2012 at 4:26 PM, Jaime Casanova <jaime@2ndquadrant.com> wrote:

On Wed, Feb 1, 2012 at 9:18 PM, Fujii Masao <masao.fujii@gmail.com> wrote:

--On 15. Januar 2012 02:50:00 -0500 Jaime Casanova <jaime@2ndquadrant.com>
wrote:

Attached is a patch thats implements a pg_stat_recovery view that
keeps counters about processed wal records. I just notice that it
still lacks documentation but i will add it during the week.

Before reviewing the patch, I'd like to know: what's the purpose of this view?
It's only debug purpose? ISTM that most users don't care about this view at all.

yeah! you're right. most users won't care about it... did i tell that
i added a track_recovery GUC so only users that wanted pay for it? i
probably did not tell that :D

If only core developer is interested in this view, ISTM that short
description for
each WAL record is not required because he or she can know the meaning of each
WAL record by reading the source code. No? Adding short descriptions for every
WAL records seems to be overkill.

Regards,

--
Fujii Masao
NIPPON TELEGRAPH AND TELEPHONE CORPORATION
NTT Open Source Software Center

#8Fujii Masao
masao.fujii@gmail.com
In reply to: Magnus Hagander (#6)
Re: pg_stats_recovery view

On Thu, Feb 2, 2012 at 4:32 PM, Magnus Hagander <magnus@hagander.net> wrote:

On Thu, Feb 2, 2012 at 08:26, Jaime Casanova <jaime@2ndquadrant.com> wrote:

On Wed, Feb 1, 2012 at 9:18 PM, Fujii Masao <masao.fujii@gmail.com> wrote:

--On 15. Januar 2012 02:50:00 -0500 Jaime Casanova <jaime@2ndquadrant.com>
wrote:

Attached is a patch thats implements a pg_stat_recovery view that
keeps counters about processed wal records. I just notice that it
still lacks documentation but i will add it during the week.

Before reviewing the patch, I'd like to know: what's the purpose of this view?
It's only debug purpose? ISTM that most users don't care about this view at all.

yeah! you're right. most users won't care about it... did i tell that
i added a track_recovery GUC so only users that wanted pay for it? i
probably did not tell that :D

I haven't looked through the code in detail, but one direct comment:
do we really need/want to send this through the stats collector? It
will only ever have one sender - perhaps we should just either store
it in shared memory or store it locally and only send it on demand?

Agreed. I think we should either.

Regards,

--
Fujii Masao
NIPPON TELEGRAPH AND TELEPHONE CORPORATION
NTT Open Source Software Center

#9Bernd Helmle
mailings@oopsware.de
In reply to: Fujii Masao (#7)
Re: pg_stats_recovery view

--On 2. Februar 2012 17:12:11 +0900 Fujii Masao <masao.fujii@gmail.com> wrote:

If only core developer is interested in this view, ISTM that short
description for
each WAL record is not required because he or she can know the meaning of each
WAL record by reading the source code. No? Adding short descriptions for every
WAL records seems to be overkill.

Yes, for a developer option alone adding all these *_short_desc functions looks
too much code for too less benefit. However, if someone with less code affinity
is interested to debug his server during recovery, it might be easier for him
to interpret the statistic counters.

Unfortunately i didn't manage to do it this week, but what i'm also interested
in is how large the performance regression is if the track_recovery variable is
activated. Not sure wether it really makes a big difference, but maybe
debugging recovery from a large archive could slow down to a degree, where you
want the GUC but can't afford it?

And, for display purposes, when this is intended for developers only, shouldn't
it be treated like all the other debug options as a DEVELOPER_OPTION, too?

--
Thanks

Bernd

#10Jaime Casanova
jaime@2ndquadrant.com
In reply to: Magnus Hagander (#6)
Re: pg_stats_recovery view

On Thu, Feb 2, 2012 at 2:32 AM, Magnus Hagander <magnus@hagander.net> wrote:

I haven't looked through the code in detail, but one direct comment:
do we really need/want to send this through the stats collector? It
will only ever have one sender - perhaps we should just either store
it in shared memory or store it locally and only send it on demand?

fyi, i intend to send a reworked patch later today, it will store the
info locally and send it on demand.
about the _short_desc functions, i added that because i wanted to
understand what was happening during recovery and the wal_record_type
(xl_info) being a number is not that clear

--
Jaime Casanova         www.2ndQuadrant.com
Professional PostgreSQL: Soporte 24x7 y capacitación

#11Alvaro Herrera
alvherre@commandprompt.com
In reply to: Jaime Casanova (#10)
Re: pg_stats_recovery view

Excerpts from Jaime Casanova's message of mar feb 14 04:10:58 -0300 2012:

On Thu, Feb 2, 2012 at 2:32 AM, Magnus Hagander <magnus@hagander.net> wrote:

I haven't looked through the code in detail, but one direct comment:
do we really need/want to send this through the stats collector? It
will only ever have one sender - perhaps we should just either store
it in shared memory or store it locally and only send it on demand?

fyi, i intend to send a reworked patch later today, it will store the
info locally and send it on demand.
about the _short_desc functions, i added that because i wanted to
understand what was happening during recovery and the wal_record_type
(xl_info) being a number is not that clear

Maybe it'd be clearer if you display it in hex and filter out just the
bits that are interesting for this use? IIRC xl_info carries some other
bits than the ones to identify the record type, which could be
confusing.

--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support

#12Fujii Masao
masao.fujii@gmail.com
In reply to: Jaime Casanova (#10)
Re: pg_stats_recovery view

On Tue, Feb 14, 2012 at 4:10 PM, Jaime Casanova <jaime@2ndquadrant.com> wrote:

On Thu, Feb 2, 2012 at 2:32 AM, Magnus Hagander <magnus@hagander.net> wrote:

I haven't looked through the code in detail, but one direct comment:
do we really need/want to send this through the stats collector? It
will only ever have one sender - perhaps we should just either store
it in shared memory or store it locally and only send it on demand?

fyi, i intend to send a reworked patch later today, it will store the
info locally and send it on demand.

Jaime,
are you planning to submit the updated version of the patch?

Regards,

--
Fujii Masao
NIPPON TELEGRAPH AND TELEPHONE CORPORATION
NTT Open Source Software Center

#13Robert Haas
robertmhaas@gmail.com
In reply to: Fujii Masao (#12)
Re: pg_stats_recovery view

On Fri, Mar 9, 2012 at 7:20 AM, Fujii Masao <masao.fujii@gmail.com> wrote:

On Tue, Feb 14, 2012 at 4:10 PM, Jaime Casanova <jaime@2ndquadrant.com> wrote:

On Thu, Feb 2, 2012 at 2:32 AM, Magnus Hagander <magnus@hagander.net> wrote:

I haven't looked through the code in detail, but one direct comment:
do we really need/want to send this through the stats collector? It
will only ever have one sender - perhaps we should just either store
it in shared memory or store it locally and only send it on demand?

fyi, i intend to send a reworked patch later today, it will store the
info locally and send it on demand.

Jaime,
are you planning to submit the updated version of the patch?

Hearing no response, I have marked this patch Returned with Feedback.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company