diff -crN ../src.orig/backend/access/heap/heapam.c ./backend/access/heap/heapam.c
*** ../src.orig/backend/access/heap/heapam.c	Sat Jun  2 11:17:47 2001
--- ./backend/access/heap/heapam.c	Sat Jun  2 11:18:33 2001
***************
*** 87,92 ****
--- 87,93 ----
  #include "utils/relcache.h"
  
  #include "access/xlogutils.h"
+ #include "pgstat.h"
  
  XLogRecPtr log_heap_move(Relation reln, Buffer oldbuf, ItemPointerData from,
  			  Buffer newbuf, HeapTuple newtup);
***************
*** 595,600 ****
--- 596,603 ----
  	if (lockmode != NoLock)
  		LockRelation(r, lockmode);
  
+ 	pgstat_initstats(&r->pgstat_info, r);
+ 
  	return r;
  }
  
***************
*** 632,637 ****
--- 635,642 ----
  	if (lockmode != NoLock)
  		LockRelation(r, lockmode);
  
+ 	pgstat_initstats(&r->pgstat_info, r);
+ 
  	return r;
  }
  
***************
*** 662,667 ****
--- 667,675 ----
  	if (RelationIsValid(r) && r->rd_rel->relkind == RELKIND_INDEX)
  		elog(ERROR, "%s is an index relation", RelationGetRelationName(r));
  
+ 	if (RelationIsValid(r))
+ 		pgstat_initstats(&r->pgstat_info, r);
+ 
  	return r;
  }
  
***************
*** 692,697 ****
--- 700,708 ----
  	if (RelationIsValid(r) && r->rd_rel->relkind == RELKIND_INDEX)
  		elog(ERROR, "%s is an index relation", RelationGetRelationName(r));
  
+ 	if (RelationIsValid(r))
+ 		pgstat_initstats(&r->pgstat_info, r);
+ 
  	return r;
  }
  
***************
*** 770,775 ****
--- 781,788 ----
  	scan->rs_snapshot = snapshot;
  	scan->rs_nkeys = (short) nkeys;
  
+ 	pgstat_initstats(&scan->rs_pgstat_info, relation);
+ 
  	/*
  	 * we do this here instead of in initscan() because heap_rescan also
  	 * calls initscan() and we don't want to allocate memory again
***************
*** 793,799 ****
  			bool scanFromEnd,
  			ScanKey key)
  {
- 
  	/*
  	 * increment access statistics
  	 */
--- 806,811 ----
***************
*** 810,815 ****
--- 822,829 ----
  	 */
  	scan->rs_atend = scanFromEnd;
  	initscan(scan, scan->rs_rd, scanFromEnd, scan->rs_nkeys, key);
+ 
+ 	pgstat_reset_heap_scan(&scan->rs_pgstat_info);
  }
  
  /* ----------------
***************
*** 908,913 ****
--- 922,929 ----
  	if (scan == NULL)
  		elog(ERROR, "heap_getnext: NULL relscan");
  
+ 	pgstat_count_heap_scan(&scan->rs_pgstat_info);
+ 
  	/*
  	 * initialize return buffer to InvalidBuffer
  	 */
***************
*** 1076,1081 ****
--- 1092,1100 ----
  
  	HEAPDEBUG_7;				/* heap_getnext returning tuple */
  
+ 	if (scan->rs_ctup.t_data != NULL)
+ 		pgstat_count_heap_getnext(&scan->rs_pgstat_info);
+ 
  	return ((scan->rs_ctup.t_data == NULL) ? NULL : &(scan->rs_ctup));
  }
  
***************
*** 1096,1102 ****
  heap_fetch(Relation relation,
  		   Snapshot snapshot,
  		   HeapTuple tuple,
! 		   Buffer *userbuf)
  {
  	ItemId		lp;
  	Buffer		buffer;
--- 1115,1122 ----
  heap_fetch(Relation relation,
  		   Snapshot snapshot,
  		   HeapTuple tuple,
! 		   Buffer *userbuf,
! 		   IndexScanDesc iscan)
  {
  	ItemId		lp;
  	Buffer		buffer;
***************
*** 1171,1176 ****
--- 1191,1201 ----
  		 * responsible for releasing the buffer.
  		 */
  		*userbuf = buffer;
+ 
+ 		if (iscan != NULL)
+ 			pgstat_count_heap_fetch(&iscan->xs_pgstat_info);
+ 		else
+ 			pgstat_count_heap_fetch(&relation->pgstat_info);
  	}
  }
  
***************
*** 1322,1327 ****
--- 1347,1354 ----
  	START_CRIT_SECTION();
  	RelationPutHeapTuple(relation, buffer, tup);
  
+ 	pgstat_count_heap_insert(&relation->pgstat_info);
+ 
  	/* XLOG stuff */
  	{
  		xl_heap_insert xlrec;
***************
*** 1510,1515 ****
--- 1537,1544 ----
  		heap_tuple_toast_attrs(relation, NULL, &(tp));
  #endif
  
+ 	pgstat_count_heap_delete(&relation->pgstat_info);
+ 
  	/*
  	 * Mark tuple for invalidation from system caches at next command
  	 * boundary. We have to do this before WriteBuffer because we need to
***************
*** 1768,1773 ****
--- 1797,1804 ----
  		already_marked = false;
  		newbuf = buffer;
  	}
+ 
+ 	pgstat_count_heap_update(&relation->pgstat_info);
  
  	/*
  	 * At this point newbuf and buffer are both pinned and locked,
diff -crN ../src.orig/backend/access/heap/tuptoaster.c ./backend/access/heap/tuptoaster.c
*** ../src.orig/backend/access/heap/tuptoaster.c	Sat Jun  2 11:17:47 2001
--- ./backend/access/heap/tuptoaster.c	Sat Jun  2 11:18:33 2001
***************
*** 936,942 ****
  	while ((indexRes = index_getnext(toastscan, ForwardScanDirection)) != NULL)
  	{
  		toasttup.t_self = indexRes->heap_iptr;
! 		heap_fetch(toastrel, SnapshotAny, &toasttup, &buffer);
  		pfree(indexRes);
  
  		if (!toasttup.t_data)
--- 936,942 ----
  	while ((indexRes = index_getnext(toastscan, ForwardScanDirection)) != NULL)
  	{
  		toasttup.t_self = indexRes->heap_iptr;
! 		heap_fetch(toastrel, SnapshotAny, &toasttup, &buffer, toastscan);
  		pfree(indexRes);
  
  		if (!toasttup.t_data)
***************
*** 1029,1035 ****
  	while ((indexRes = index_getnext(toastscan, ForwardScanDirection)) != NULL)
  	{
  		toasttup.t_self = indexRes->heap_iptr;
! 		heap_fetch(toastrel, SnapshotAny, &toasttup, &buffer);
  		pfree(indexRes);
  
  		if (toasttup.t_data == NULL)
--- 1029,1035 ----
  	while ((indexRes = index_getnext(toastscan, ForwardScanDirection)) != NULL)
  	{
  		toasttup.t_self = indexRes->heap_iptr;
! 		heap_fetch(toastrel, SnapshotAny, &toasttup, &buffer, toastscan);
  		pfree(indexRes);
  
  		if (toasttup.t_data == NULL)
diff -crN ../src.orig/backend/access/index/genam.c ./backend/access/index/genam.c
*** ../src.orig/backend/access/index/genam.c	Sat Jun  2 11:17:47 2001
--- ./backend/access/index/genam.c	Sat Jun  2 11:18:33 2001
***************
*** 48,53 ****
--- 48,54 ----
  #include "postgres.h"
  #include "access/genam.h"
  
+ #include "pgstat.h"
  
  /* ----------------------------------------------------------------
   *		general access method routines
***************
*** 113,118 ****
--- 114,121 ----
  	ItemPointerSetInvalid(&scan->previousMarkData);
  	ItemPointerSetInvalid(&scan->currentMarkData);
  	ItemPointerSetInvalid(&scan->nextMarkData);
+ 
+ 	pgstat_initstats(&scan->xs_pgstat_info, relation);
  
  	/*
  	 * mark cached function lookup data invalid; it will be set on first
diff -crN ../src.orig/backend/access/index/indexam.c ./backend/access/index/indexam.c
*** ../src.orig/backend/access/index/indexam.c	Sat Jun  2 11:17:47 2001
--- ./backend/access/index/indexam.c	Sat Jun  2 11:19:15 2001
***************
*** 70,75 ****
--- 70,76 ----
  #include "access/heapam.h"
  #include "utils/relcache.h"
  
+ #include "pgstat.h"
  
  /* ----------------------------------------------------------------
   *					macros used in index_ routines
***************
*** 135,140 ****
--- 136,143 ----
  	if (r->rd_rel->relkind != RELKIND_INDEX)
  		elog(ERROR, "%s is not an index relation", RelationGetRelationName(r));
  
+ 	pgstat_initstats(&r->pgstat_info, r);
+ 
  	return r;
  }
  
***************
*** 157,162 ****
--- 160,167 ----
  	if (r->rd_rel->relkind != RELKIND_INDEX)
  		elog(ERROR, "%s is not an index relation", RelationGetRelationName(r));
  
+ 	pgstat_initstats(&r->pgstat_info, r);
+ 
  	return r;
  }
  
***************
*** 256,261 ****
--- 261,268 ----
  										 UInt16GetDatum(numberOfKeys),
  										 PointerGetDatum(key)));
  
+ 	pgstat_initstats(&scan->xs_pgstat_info, relation);
+ 
  	/*
  	 * We want to look up the amgettuple procedure just once per scan,
  	 * not once per index_getnext call.  So do it here and save
***************
*** 283,288 ****
--- 290,297 ----
  					 PointerGetDatum(scan),
  					 BoolGetDatum(scanFromEnd),
  					 PointerGetDatum(key));
+ 
+ 	pgstat_reset_index_scan(&scan->xs_pgstat_info);
  }
  
  /* ----------------
***************
*** 353,358 ****
--- 362,369 ----
  
  	SCAN_CHECKS;
  
+ 	pgstat_count_index_scan(&scan->xs_pgstat_info);
+ 
  	/*
  	 * have the am's gettuple proc do all the work.
  	 * index_beginscan already set up fn_getnext.
***************
*** 362,367 ****
--- 373,380 ----
  									  PointerGetDatum(scan),
  									  Int32GetDatum(direction)));
  
+ 	if (result != NULL)
+ 		pgstat_count_index_getnext(&scan->xs_pgstat_info);
  	return result;
  }
  
diff -crN ../src.orig/backend/access/nbtree/nbtinsert.c ./backend/access/nbtree/nbtinsert.c
*** ../src.orig/backend/access/nbtree/nbtinsert.c	Sat Jun  2 11:17:47 2001
--- ./backend/access/nbtree/nbtinsert.c	Sat Jun  2 11:18:33 2001
***************
*** 209,215 ****
  			if (chtup)
  			{
  				htup.t_self = btitem->bti_itup.t_tid;
! 				heap_fetch(heapRel, SnapshotDirty, &htup, &buffer);
  				if (htup.t_data == NULL)		/* YES! */
  					break;
  				/* Live tuple is being inserted, so continue checking */
--- 209,215 ----
  			if (chtup)
  			{
  				htup.t_self = btitem->bti_itup.t_tid;
! 				heap_fetch(heapRel, SnapshotDirty, &htup, &buffer, NULL);
  				if (htup.t_data == NULL)		/* YES! */
  					break;
  				/* Live tuple is being inserted, so continue checking */
***************
*** 219,225 ****
  
  			cbti = (BTItem) PageGetItem(page, PageGetItemId(page, offset));
  			htup.t_self = cbti->bti_itup.t_tid;
! 			heap_fetch(heapRel, SnapshotDirty, &htup, &buffer);
  			if (htup.t_data != NULL)	/* it is a duplicate */
  			{
  				TransactionId xwait =
--- 219,225 ----
  
  			cbti = (BTItem) PageGetItem(page, PageGetItemId(page, offset));
  			htup.t_self = cbti->bti_itup.t_tid;
! 			heap_fetch(heapRel, SnapshotDirty, &htup, &buffer, NULL);
  			if (htup.t_data != NULL)	/* it is a duplicate */
  			{
  				TransactionId xwait =
diff -crN ../src.orig/backend/access/transam/xact.c ./backend/access/transam/xact.c
*** ../src.orig/backend/access/transam/xact.c	Sat Jun  2 11:17:48 2001
--- ./backend/access/transam/xact.c	Sat Jun  2 11:18:33 2001
***************
*** 176,181 ****
--- 176,183 ----
  #include "utils/relcache.h"
  #include "utils/temprel.h"
  
+ #include "pgstat.h"
+ 
  extern bool SharedBufferChanged;
  
  static void AbortTransaction(void);
***************
*** 1088,1093 ****
--- 1090,1098 ----
  
  	SharedBufferChanged = false;/* safest place to do it */
  
+ 	/* Count transaction commit in statistics collector */
+ 	pgstat_count_xact_commit();
+ 
  	/*
  	 * done with commit processing, set current transaction state back to
  	 * default
***************
*** 1167,1172 ****
--- 1172,1180 ----
  	CloseSequences();
  	AtEOXact_portals();
  	RecordTransactionAbort();
+ 
+ 	/* Count transaction abort in statistics collector */
+ 	pgstat_count_xact_rollback();
  
  	RelationPurgeLocalRelation(false);
  	AtEOXact_temp_relations(false);
diff -crN ../src.orig/backend/catalog/indexing.c ./backend/catalog/indexing.c
*** ../src.orig/backend/catalog/indexing.c	Sat Jun  2 11:17:48 2001
--- ./backend/catalog/indexing.c	Sat Jun  2 11:18:33 2001
***************
*** 204,210 ****
  	while ((indexRes = index_getnext(sd, ForwardScanDirection)))
  	{
  		tuple.t_self = indexRes->heap_iptr;
! 		heap_fetch(heapRelation, SnapshotNow, &tuple, &buffer);
  		pfree(indexRes);
  		if (tuple.t_data != NULL)
  			break;
--- 204,210 ----
  	while ((indexRes = index_getnext(sd, ForwardScanDirection)))
  	{
  		tuple.t_self = indexRes->heap_iptr;
! 		heap_fetch(heapRelation, SnapshotNow, &tuple, &buffer, sd);
  		pfree(indexRes);
  		if (tuple.t_data != NULL)
  			break;
diff -crN ../src.orig/backend/catalog/pg_largeobject.c ./backend/catalog/pg_largeobject.c
*** ../src.orig/backend/catalog/pg_largeobject.c	Sat Jun  2 11:17:48 2001
--- ./backend/catalog/pg_largeobject.c	Sat Jun  2 11:18:33 2001
***************
*** 114,120 ****
  	while ((indexRes = index_getnext(sd, ForwardScanDirection)))
  	{
  		tuple.t_self = indexRes->heap_iptr;
! 		heap_fetch(pg_largeobject, SnapshotNow, &tuple, &buffer);
  		pfree(indexRes);
  		if (tuple.t_data != NULL)
  		{
--- 114,120 ----
  	while ((indexRes = index_getnext(sd, ForwardScanDirection)))
  	{
  		tuple.t_self = indexRes->heap_iptr;
! 		heap_fetch(pg_largeobject, SnapshotNow, &tuple, &buffer, sd);
  		pfree(indexRes);
  		if (tuple.t_data != NULL)
  		{
***************
*** 165,171 ****
  	while ((indexRes = index_getnext(sd, ForwardScanDirection)))
  	{
  		tuple.t_self = indexRes->heap_iptr;
! 		heap_fetch(pg_largeobject, SnapshotNow, &tuple, &buffer);
  		pfree(indexRes);
  		if (tuple.t_data != NULL)
  		{
--- 165,171 ----
  	while ((indexRes = index_getnext(sd, ForwardScanDirection)))
  	{
  		tuple.t_self = indexRes->heap_iptr;
! 		heap_fetch(pg_largeobject, SnapshotNow, &tuple, &buffer, sd);
  		pfree(indexRes);
  		if (tuple.t_data != NULL)
  		{
diff -crN ../src.orig/backend/commands/analyze.c ./backend/commands/analyze.c
*** ../src.orig/backend/commands/analyze.c	Sat Jun  2 11:17:48 2001
--- ./backend/commands/analyze.c	Sat Jun  2 11:18:33 2001
***************
*** 607,613 ****
  				goto pageloop;
  			}
  			ItemPointerSet(&targtuple.t_self, targblock, targoffset);
! 			heap_fetch(onerel, SnapshotNow, &targtuple, &targbuffer);
  			if (targtuple.t_data != NULL)
  			{
  				/*
--- 607,613 ----
  				goto pageloop;
  			}
  			ItemPointerSet(&targtuple.t_self, targblock, targoffset);
! 			heap_fetch(onerel, SnapshotNow, &targtuple, &targbuffer, NULL);
  			if (targtuple.t_data != NULL)
  			{
  				/*
diff -crN ../src.orig/backend/commands/cluster.c ./backend/commands/cluster.c
*** ../src.orig/backend/commands/cluster.c	Sat Jun  2 11:17:48 2001
--- ./backend/commands/cluster.c	Sat Jun  2 11:18:33 2001
***************
*** 264,270 ****
  		LocalHeapTuple.t_self = ScanResult->heap_iptr;
  		LocalHeapTuple.t_datamcxt = NULL;
  		LocalHeapTuple.t_data = NULL;
! 		heap_fetch(LocalOldHeap, SnapshotNow, &LocalHeapTuple, &LocalBuffer);
  		if (LocalHeapTuple.t_data != NULL)
  		{
  
--- 264,271 ----
  		LocalHeapTuple.t_self = ScanResult->heap_iptr;
  		LocalHeapTuple.t_datamcxt = NULL;
  		LocalHeapTuple.t_data = NULL;
! 		heap_fetch(LocalOldHeap, SnapshotNow, &LocalHeapTuple, &LocalBuffer,
! 						ScanDesc);
  		if (LocalHeapTuple.t_data != NULL)
  		{
  
diff -crN ../src.orig/backend/commands/trigger.c ./backend/commands/trigger.c
*** ../src.orig/backend/commands/trigger.c	Sat Jun  2 11:17:48 2001
--- ./backend/commands/trigger.c	Sat Jun  2 11:18:33 2001
***************
*** 551,557 ****
  				break;
  
  			tuple.t_self = indexRes->heap_iptr;
! 			heap_fetch(tgrel, SnapshotNow, &tuple, &buffer);
  			pfree(indexRes);
  			if (!tuple.t_data)
  				continue;
--- 551,557 ----
  				break;
  
  			tuple.t_self = indexRes->heap_iptr;
! 			heap_fetch(tgrel, SnapshotNow, &tuple, &buffer, sd);
  			pfree(indexRes);
  			if (!tuple.t_data)
  				continue;
***************
*** 1394,1400 ****
  	if (ItemPointerIsValid(&(event->dte_oldctid)))
  	{
  		ItemPointerCopy(&(event->dte_oldctid), &(oldtuple.t_self));
! 		heap_fetch(rel, SnapshotAny, &oldtuple, &oldbuffer);
  		if (!oldtuple.t_data)
  			elog(ERROR, "DeferredTriggerExecute: failed to fetch old tuple");
  	}
--- 1394,1400 ----
  	if (ItemPointerIsValid(&(event->dte_oldctid)))
  	{
  		ItemPointerCopy(&(event->dte_oldctid), &(oldtuple.t_self));
! 		heap_fetch(rel, SnapshotAny, &oldtuple, &oldbuffer, NULL);
  		if (!oldtuple.t_data)
  			elog(ERROR, "DeferredTriggerExecute: failed to fetch old tuple");
  	}
***************
*** 1402,1408 ****
  	if (ItemPointerIsValid(&(event->dte_newctid)))
  	{
  		ItemPointerCopy(&(event->dte_newctid), &(newtuple.t_self));
! 		heap_fetch(rel, SnapshotAny, &newtuple, &newbuffer);
  		if (!newtuple.t_data)
  			elog(ERROR, "DeferredTriggerExecute: failed to fetch new tuple");
  	}
--- 1402,1408 ----
  	if (ItemPointerIsValid(&(event->dte_newctid)))
  	{
  		ItemPointerCopy(&(event->dte_newctid), &(newtuple.t_self));
! 		heap_fetch(rel, SnapshotAny, &newtuple, &newbuffer, NULL);
  		if (!newtuple.t_data)
  			elog(ERROR, "DeferredTriggerExecute: failed to fetch new tuple");
  	}
***************
*** 1861,1867 ****
  					break;
  
  				tuple.t_self = indexRes->heap_iptr;
! 				heap_fetch(tgrel, SnapshotNow, &tuple, &buffer);
  				pfree(indexRes);
  				if (!tuple.t_data)
  					continue;
--- 1861,1867 ----
  					break;
  
  				tuple.t_self = indexRes->heap_iptr;
! 				heap_fetch(tgrel, SnapshotNow, &tuple, &buffer, sd);
  				pfree(indexRes);
  				if (!tuple.t_data)
  					continue;
diff -crN ../src.orig/backend/commands/vacuum.c ./backend/commands/vacuum.c
*** ../src.orig/backend/commands/vacuum.c	Sat Jun  2 11:17:48 2001
--- ./backend/commands/vacuum.c	Sat Jun  2 11:18:33 2001
***************
*** 48,53 ****
--- 48,55 ----
  #include "utils/syscache.h"
  #include "utils/temprel.h"
  
+ #include "pgstat.h"
+ 
  extern XLogRecPtr log_heap_clean(Relation reln, Buffer buffer,
  			   char *unused, int unlen);
  extern XLogRecPtr log_heap_move(Relation reln,
***************
*** 185,190 ****
--- 187,197 ----
  	if (IsTransactionBlock())
  		elog(ERROR, "%s cannot run inside a BEGIN/END block", stmttype);
  
+ 	/*
+ 	 * Send info about dead objects to the statistics collector
+ 	 */
+ 	pgstat_vacuum_tabstat();
+ 
  	if (vacstmt->verbose)
  		MESSAGE_LEVEL = NOTICE;
  	else
***************
*** 2345,2351 ****
  	/* get the buffer cache tuple */
  	rtup.t_self = ctup->t_self;
  	ReleaseSysCache(ctup);
! 	heap_fetch(rd, SnapshotNow, &rtup, &buffer);
  
  	/* overwrite the existing statistics in the tuple */
  	pgcform = (Form_pg_class) GETSTRUCT(&rtup);
--- 2352,2358 ----
  	/* get the buffer cache tuple */
  	rtup.t_self = ctup->t_self;
  	ReleaseSysCache(ctup);
! 	heap_fetch(rd, SnapshotNow, &rtup, &buffer, NULL);
  
  	/* overwrite the existing statistics in the tuple */
  	pgcform = (Form_pg_class) GETSTRUCT(&rtup);
diff -crN ../src.orig/backend/executor/execMain.c ./backend/executor/execMain.c
*** ../src.orig/backend/executor/execMain.c	Sat Jun  2 11:17:48 2001
--- ./backend/executor/execMain.c	Sat Jun  2 11:18:33 2001
***************
*** 1671,1677 ****
  	{
  		Buffer		buffer;
  
! 		heap_fetch(relation, SnapshotDirty, &tuple, &buffer);
  		if (tuple.t_data != NULL)
  		{
  			TransactionId xwait = SnapshotDirty->xmax;
--- 1671,1677 ----
  	{
  		Buffer		buffer;
  
! 		heap_fetch(relation, SnapshotDirty, &tuple, &buffer, NULL);
  		if (tuple.t_data != NULL)
  		{
  			TransactionId xwait = SnapshotDirty->xmax;
diff -crN ../src.orig/backend/executor/nodeIndexscan.c ./backend/executor/nodeIndexscan.c
*** ../src.orig/backend/executor/nodeIndexscan.c	Sat Jun  2 11:17:48 2001
--- ./backend/executor/nodeIndexscan.c	Sat Jun  2 11:18:33 2001
***************
*** 173,179 ****
  		while ((result = index_getnext(scandesc, direction)) != NULL)
  		{
  			tuple->t_self = result->heap_iptr;
! 			heap_fetch(heapRelation, snapshot, tuple, &buffer);
  			pfree(result);
  
  			if (tuple->t_data != NULL)
--- 173,179 ----
  		while ((result = index_getnext(scandesc, direction)) != NULL)
  		{
  			tuple->t_self = result->heap_iptr;
! 			heap_fetch(heapRelation, snapshot, tuple, &buffer, scandesc);
  			pfree(result);
  
  			if (tuple->t_data != NULL)
diff -crN ../src.orig/backend/executor/nodeTidscan.c ./backend/executor/nodeTidscan.c
*** ../src.orig/backend/executor/nodeTidscan.c	Sat Jun  2 11:17:48 2001
--- ./backend/executor/nodeTidscan.c	Sat Jun  2 11:18:33 2001
***************
*** 152,158 ****
  		if (itemptr)
  		{
  			tuple->t_self = *(itemptr);
! 			heap_fetch(heapRelation, snapshot, tuple, &buffer);
  		}
  		if (tuple->t_data != NULL)
  		{
--- 152,158 ----
  		if (itemptr)
  		{
  			tuple->t_self = *(itemptr);
! 			heap_fetch(heapRelation, snapshot, tuple, &buffer, NULL);
  		}
  		if (tuple->t_data != NULL)
  		{
diff -crN ../src.orig/backend/parser/parse_func.c ./backend/parser/parse_func.c
*** ../src.orig/backend/parser/parse_func.c	Sat Jun  2 11:17:48 2001
--- ./backend/parser/parse_func.c	Sat Jun  2 11:18:33 2001
***************
*** 752,758 ****
  			tuple.t_datamcxt = NULL;
  			tuple.t_data = NULL;
  			tuple.t_self = indexRes->heap_iptr;
! 			heap_fetch(heapRelation, SnapshotNow, &tuple, &buffer);
  			pfree(indexRes);
  			if (tuple.t_data != NULL)
  			{
--- 752,758 ----
  			tuple.t_datamcxt = NULL;
  			tuple.t_data = NULL;
  			tuple.t_self = indexRes->heap_iptr;
! 			heap_fetch(heapRelation, SnapshotNow, &tuple, &buffer, sd);
  			pfree(indexRes);
  			if (tuple.t_data != NULL)
  			{
diff -crN ../src.orig/backend/postmaster/Makefile ./backend/postmaster/Makefile
*** ../src.orig/backend/postmaster/Makefile	Sat Jun  2 11:17:49 2001
--- ./backend/postmaster/Makefile	Sat Jun  2 11:18:33 2001
***************
*** 12,18 ****
  top_builddir = ../../..
  include $(top_builddir)/src/Makefile.global
  
! OBJS = postmaster.o
  
  all: SUBSYS.o
  
--- 12,18 ----
  top_builddir = ../../..
  include $(top_builddir)/src/Makefile.global
  
! OBJS = postmaster.o pgstat.o
  
  all: SUBSYS.o
  
diff -crN ../src.orig/backend/postmaster/pgstat.c ./backend/postmaster/pgstat.c
*** ../src.orig/backend/postmaster/pgstat.c	Wed Dec 31 19:00:00 1969
--- ./backend/postmaster/pgstat.c	Sat Jun  2 11:18:33 2001
***************
*** 0 ****
--- 1,2809 ----
+ /* ----------
+  * pgstat.c
+  *
+  *	All the statistics collector stuff hacked up in one big, ugly file.
+  *
+  *	TODO:	- Separate collector, postmaster and backend stuff
+  *			  into different files.
+  *
+  *			- Add some automatic call for pgstat vacuuming.
+  *
+  *			- Add a pgstat config column to pg_database, so this
+  *			  entire thing can be enabled/disabled on a per db base.
+  *			  Not to be done before 7.2 - requires catalog change and
+  *			  thus an initdb and we might want to provide this as a
+  *			  patch for 7.1.
+  *
+  *			- Make the functions from contrib/pgstat_tmp builtin
+  *			  and create the views on initdb.
+  *
+  *	Copyright (c) 2001, PostgreSQL Global Development Group
+  *
+  *	$Id:$
+  * ----------
+  */
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <unistd.h>
+ #include <fcntl.h>
+ 
+ #include <sys/param.h>
+ #include <sys/time.h>
+ #include <sys/types.h>
+ #include <sys/socket.h>
+ #include <netinet/in.h>
+ #include <arpa/inet.h>
+ 
+ #include <errno.h>
+ #include <signal.h>
+ 
+ #include "postgres.h"
+ 
+ #include "miscadmin.h"
+ #include "utils/memutils.h"
+ #include "storage/backendid.h"
+ #include "utils/rel.h"
+ #include "utils/hsearch.h"
+ #include "utils/syscache.h"
+ #include "access/xact.h"
+ #include "access/heapam.h"
+ #include "catalog/catname.h"
+ #include "catalog/pg_shadow.h"
+ #include "catalog/pg_database.h"
+ 
+ #include "pgstat.h"
+ 
+ 
+ /* ----------
+  * Global data
+  * ----------
+  */
+ 
+ 
+ /* ----------
+  * Local data
+  * ----------
+  */
+ static int					pgStatSock = -1;
+ static int					pgStatPipe[2];
+ static struct sockaddr_in	pgStatAddr;
+ static int					pgStatPmPipe[2];
+ 
+ static int					pgStatRunning = 0;
+ static int					pgStatPid;
+ 
+ static long					pgStatNumMessages = 0;
+ 
+ static bool					pgStatRunningInCollector = FALSE;
+ static int					pgStatTabstatAlloc = 0;
+ static int					pgStatTabstatUsed = 0;
+ static PgStat_MsgTabstat  **pgStatTabstatMessages = NULL;
+ static int					pgStatXactCommit = 0;
+ static int					pgStatXactRollback = 0;
+ 
+ 
+ static TransactionId		pgStatDBHashXact = InvalidTransactionId;
+ static HTAB				   *pgStatDBHash = NULL;
+ static HTAB				   *pgStatBeDead = NULL;
+ static PgStat_StatBeEntry  *pgStatBeTable = NULL;
+ static int					pgStatNumBackends = 0;
+ 
+ static char					pgStat_tmpfname[PATH_MAX];
+ static char					pgStat_fname[PATH_MAX];
+ 
+ 
+ /* ----------
+  * Local function forward declarations
+  * ----------
+  */
+ static void		pgstat_main(void);
+ static void		pgstat_recvbuffer(void);
+ 
+ static int		pgstat_add_backend(PgStat_MsgHdr *msg);
+ static void		pgstat_sub_backend(int procpid);
+ static void		pgstat_drop_database(Oid databaseid);
+ static void		pgstat_write_statsfile(void);
+ static void		pgstat_read_statsfile(HTAB **dbhash, Oid onlydb, 
+ 									PgStat_StatBeEntry **betab, 
+ 									int *numbackends);
+ 
+ static void		pgstat_setheader(PgStat_MsgHdr *hdr, int mtype);
+ static void		pgstat_send(void *msg, int len);
+ 
+ static void		pgstat_recv_bestart(PgStat_MsgBestart *msg, int len);
+ static void		pgstat_recv_beterm(PgStat_MsgBeterm *msg, int len);
+ static void		pgstat_recv_activity(PgStat_MsgActivity *msg, int len);
+ static void		pgstat_recv_tabstat(PgStat_MsgTabstat *msg, int len);
+ static void		pgstat_recv_tabpurge(PgStat_MsgTabpurge *msg, int len);
+ static void		pgstat_recv_dropdb(PgStat_MsgDropdb *msg, int len);
+ static void		pgstat_recv_resetcounter(PgStat_MsgResetcounter *msg, int len);
+ 
+ 
+ /* ------------------------------------------------------------
+  * Public functions called from postmaster follow
+  * ------------------------------------------------------------
+  */
+ 
+ 
+ /* ----------
+  * pgstat_init() -
+  *
+  *	Called from postmaster at startup. Create the resources required
+  *	by the statistics collector process.
+  * ----------
+  */
+ int
+ pgstat_init(void)
+ {
+ 	int			alen;
+ 
+ 	/*
+ 	 * Initialize the filenames for the status reports.
+ 	 */
+ 	snprintf(pgStat_tmpfname,  PATH_MAX - 1, 
+ 				PGSTAT_STAT_TMPFILE,  DataDir, getpid());
+ 	snprintf(pgStat_fname, PATH_MAX - 1, 
+ 				PGSTAT_STAT_FILENAME, DataDir);
+ 
+ 	/*
+ 	 * Create the UDP socket for receiving statistic messages
+ 	 */
+ 	if ((pgStatSock = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
+ 	{
+ 		perror("PGSTAT: socket(2)");
+ 		return -1;
+ 	}
+ 
+ 	/*
+ 	 * Bind it to a kernel assigned port on localhost and
+ 	 * get the assigned port via getsockname().
+ 	 */
+ 	pgStatAddr.sin_family	= AF_INET;
+ 	pgStatAddr.sin_port	= htons(0);
+ 	inet_aton("127.0.0.1", &(pgStatAddr.sin_addr));
+ 	alen = sizeof(pgStatAddr);
+ 	if (bind(pgStatSock, (struct sockaddr *)&pgStatAddr, alen) < 0)
+ 	{
+ 		perror("PGSTAT: bind(2)");
+ 		close(pgStatSock);
+ 		pgStatSock = -1;
+ 		return -1;
+ 	}
+ 	if (getsockname(pgStatSock, (struct sockaddr *)&pgStatAddr, &alen) < 0)
+ 	{
+ 		perror("PGSTAT: getsockname(2)");
+ 		close(pgStatSock);
+ 		pgStatSock = -1;
+ 		return -1;
+ 	}
+ 
+ 	/*
+ 	 * Set the socket to non-blocking IO
+ 	 */
+ 	if (fcntl(pgStatSock, F_SETFL, O_NONBLOCK) < 0)
+ 	{
+ 		perror("PGSTAT: fcntl(2)");
+ 		close(pgStatSock);
+ 		pgStatSock = -1;
+ 		return -1;
+ 	}
+ 
+ 	/*
+ 	 * Create the pipe that controls the statistics collector shutdown
+ 	 */
+ 	if (pipe(pgStatPmPipe) < 0)
+ 	{
+ 		perror("PGSTAT: pipe(2)");
+ 		close(pgStatSock);
+ 		pgStatSock = -1;
+ 		return -1;
+ 	}
+ 
+ 	return 0;
+ }
+ 
+ 
+ /* ----------
+  * pgstat_start() -
+  *
+  *	Called from postmaster at startup or after an existing collector
+  *	died. Fire up a fresh statistics collector.
+  * ----------
+  */
+ int
+ pgstat_start(void)
+ {
+ 	/*
+ 	 * Check that the socket at least is there
+ 	 */
+ 	if (pgStatSock < 0)
+ 	{
+ 		fprintf(stderr, 
+ 			"PGSTAT: suppress collector startup due to missing socket\n");
+ 		return 0;
+ 	}
+ 
+ 	/*
+ 	 * Then fork off the collector.
+ 	 */
+ 	switch(pgStatPid = (int)fork())
+ 	{
+ 		case -1:
+ 			perror("PGSTAT: fork(2)");
+ 			pgStatRunning = 0;
+ 			return -1;
+ 
+ 		case 0:
+ 			break;
+ 
+ 		default:
+ 			pgStatRunning = 1;
+ 			return 0;
+ 	}
+ 
+ 	pgstat_main();
+ 	exit(0);
+ }
+ 
+ 
+ /* ----------
+  * pgstat_ispgstat() -
+  *
+  *	Called from postmaster to check if a terminated child process
+  *	was the statistics collector.
+  * ----------
+  */
+ int
+ pgstat_ispgstat(int pid)
+ {
+ 	if (pgStatRunning == 0)
+ 		return 0;
+ 
+ 	if (pgStatPid != pid)
+ 		return 0;
+ 
+ 	return 1;
+ }
+ 
+ 
+ /* ----------
+  * pgstat_beterm() -
+  *
+  *	Called from postmaster to tell collector a backend terminated.
+  * ----------
+  */
+ void
+ pgstat_beterm(int pid)
+ {
+ 	PgStat_MsgBeterm		msg;
+ 
+ 	msg.m_hdr.m_type		= PGSTAT_MTYPE_BETERM;
+ 	msg.m_hdr.m_backendid	= 0;
+ 	msg.m_hdr.m_procpid		= pid;
+ 	msg.m_hdr.m_databaseid	= 0;
+ 
+ 	pgstat_send(&msg, sizeof(msg));
+ }
+ 
+ 
+ /* ------------------------------------------------------------
+  * Public functions used by backends follow
+  *------------------------------------------------------------ 
+  */
+ 
+ 
+ /* ----------
+  * pgstat_bestart() -
+  *
+  *	Tell the collector that this new backend is soon ready to process
+  *	queries. Called from tcop/postgres.c before entering the mainloop.
+  * ----------
+  */
+ void
+ pgstat_bestart(void)
+ {
+ 	PgStat_MsgBestart		msg;
+ 
+ 	if (pgStatSock < 0)
+ 		return;
+ 
+ 	pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_BESTART);
+ 	pgstat_send(&msg, sizeof(msg));
+ }
+ 
+ 
+ /* ----------
+  * pgstat_report_activity() -
+  *
+  *	Called in tcop/postgres.c to tell the collector what the backend
+  *	is actually doing (usually "<IDLE>" or the start of the query to
+  *	be executed).
+  * ----------
+  */
+ void
+ pgstat_report_activity(char *what)
+ {
+ 	PgStat_MsgActivity	msg;
+ 	int						len;
+ 
+ 	if (pgStatSock < 0)
+ 		return;
+ 
+ 	len = strlen(what);
+ 	if (len >= PGSTAT_ACTIVITY_SIZE)
+ 		len = PGSTAT_ACTIVITY_SIZE - 1;
+ 	
+ 	memcpy(msg.m_what, what, len);
+ 	msg.m_what[len] = '\0';
+ 	len += offsetof(PgStat_MsgActivity, m_what) + 1;
+ 
+ 	pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_ACTIVITY);
+ 	pgstat_send(&msg, len);
+ }
+ 
+ 
+ /* ----------
+  * pgstat_report_tabstat() -
+  *
+  *	Called from tcop/postgres.c to send the so far collected
+  *	per table access statistics to the collector.
+  * ----------
+  */
+ void
+ pgstat_report_tabstat(void)
+ {
+ 	int				i;
+ 	int				n;
+ 	int				len;
+ 
+ 	if (pgStatSock < 0)
+ 		return;
+ 
+ 	/*
+ 	 * For each message buffer used during the last query
+ 	 * set the header fields and send it out.
+ 	 */
+ 	for (i = 0; i < pgStatTabstatUsed; i++)
+ 	{
+ 		n = pgStatTabstatMessages[i]->m_nentries;
+ 		len = offsetof(PgStat_MsgTabstat, m_entry[n]);
+ 
+ 		pgStatTabstatMessages[i]->m_xact_commit   = pgStatXactCommit;
+ 		pgStatTabstatMessages[i]->m_xact_rollback = pgStatXactRollback;
+ 		pgStatXactCommit   = 0;
+ 		pgStatXactRollback = 0;
+ 
+ 		pgstat_setheader(&pgStatTabstatMessages[i]->m_hdr, 
+ 					PGSTAT_MTYPE_TABSTAT);
+ 		pgstat_send(pgStatTabstatMessages[i], len);
+ 	}
+ 
+ 	pgStatTabstatUsed = 0;
+ }
+ 
+ 
+ /* ----------
+  * pgstat_vacuum_tabstat() -
+  *
+  *	Will tell the collector about objects he can get rid of.
+  * ----------
+  */
+ int
+ pgstat_vacuum_tabstat(void)
+ {
+ 	Relation				dbrel;
+ 	HeapScanDesc			dbscan;
+ 	HeapTuple				dbtup;
+ 	Oid					   *dbidlist;
+ 	int						dbidalloc;
+ 	int						dbidused;
+ 	HASH_SEQ_STATUS			hstat;
+ 	long				   *hentry;
+ 	PgStat_StatDBEntry	   *dbentry;
+ 	PgStat_StatTabEntry	   *tabentry;
+ 	HeapTuple				reltup;
+ 	bool					found;
+ 	int						nobjects = 0;
+ 	PgStat_MsgTabpurge		msg;
+ 	int						len;
+ 	int						i;
+ 
+ 	if (pgStatSock < 0)
+ 		return 0;
+ 
+ 	/*
+ 	 * We don't vacuum inside of transaction blocks, because 
+ 	 * a possible later rollback might reactivate objects we
+ 	 * didn't find because of earlier destruction in the xact.
+ 	 */
+ 	if (IsTransactionBlock())
+ 		return 0;
+ 
+ 	/*
+ 	 * If not done for this transaction, read the statistics collector
+ 	 * stats file into some hash tables.
+ 	 */
+ 	if (pgStatDBHashXact != GetCurrentTransactionId())
+ 	{
+ 		pgstat_read_statsfile(&pgStatDBHash, MyDatabaseId, 
+ 						&pgStatBeTable, &pgStatNumBackends);
+ 		pgStatDBHashXact = GetCurrentTransactionId();
+ 	}
+ 
+ 	/*
+ 	 * Lookup our own database entry
+ 	 */
+ 	dbentry = (PgStat_StatDBEntry *)hash_search(pgStatDBHash,
+ 					(char *)&MyDatabaseId, HASH_FIND, &found);
+ 	if (!found || dbentry == NULL)
+ 		return -1;
+ 
+ 	if (dbentry->tables == NULL)
+ 		return 0;
+ 
+ 	/*
+ 	 * Initialize our messages table counter to zero
+ 	 */
+ 	msg.m_nentries = 0;
+ 
+ 	/*
+ 	 * Check for all tables if they still exist.
+ 	 */
+     hash_seq_init(&hstat, dbentry->tables);
+ 	while((hentry = hash_seq_search(&hstat)) != (long *)TRUE)
+ 	{
+ 		if (hentry == NULL)
+ 			return -1;
+ 
+ 		/*
+ 		 * Check if this relation is still alive by
+ 		 * looking up it's pg_class tuple in the 
+ 		 * system catalog cache.
+ 		 */
+ 		tabentry = (PgStat_StatTabEntry *)hentry;
+ 		reltup = SearchSysCache(RELOID,
+ 						ObjectIdGetDatum(tabentry->tableid),
+ 						0, 0, 0);
+ 		if (HeapTupleIsValid(reltup))
+ 		{
+ 			ReleaseSysCache(reltup);
+ 			continue;
+ 		}
+ 
+ 		/*
+ 		 * Add this tables Oid to the message
+ 		 */
+ 		msg.m_tableid[msg.m_nentries++] = tabentry->tableid;
+ 		nobjects++;
+ 
+ 		/*
+ 		 * If the message is full, send it out and reinitialize ot zero
+ 		 */
+ 		if (msg.m_nentries >= PGSTAT_NUM_TABPURGE)
+ 		{
+ 			len = offsetof(PgStat_MsgTabpurge, m_tableid[msg.m_nentries]);
+ 
+ 			pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_TABPURGE);
+ 			pgstat_send(&msg, len);
+ 
+ 			msg.m_nentries = 0;
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * Send the rest
+ 	 */
+ 	if (msg.m_nentries > 0)
+ 	{
+ 		len = offsetof(PgStat_MsgTabpurge, m_tableid[msg.m_nentries]);
+ 
+ 		pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_TABPURGE);
+ 		pgstat_send(&msg, len);
+ 	}
+ 
+ 	/*
+ 	 * Read pg_database and remember the Oid's of all existing databases
+ 	 */
+ 	dbidalloc = 256;
+ 	dbidused = 0;
+ 	dbidlist = (Oid *)palloc(sizeof(Oid) * dbidalloc);
+ 
+ 	dbrel = heap_openr(DatabaseRelationName, AccessShareLock);
+ 	dbscan = heap_beginscan(dbrel, 0, SnapshotNow, 0, NULL);
+ 	while (HeapTupleIsValid(dbtup = heap_getnext(dbscan, FALSE)))
+ 	{
+ 		if (dbidused >= dbidalloc)
+ 		{
+ 			dbidalloc *= 2;
+ 			dbidlist = (Oid *)repalloc((char *)dbidlist,
+ 							sizeof(Oid) * dbidalloc);
+ 		}
+ 		dbidlist[dbidused++] = dbtup->t_data->t_oid;
+ 	}
+ 	heap_endscan(dbscan);
+ 	heap_close(dbrel, AccessShareLock);
+ 
+ 	/*
+ 	 * Search the database hash table for dead databases and
+ 	 * tell the collector to drop them as well.
+ 	 */
+ 	hash_seq_init(&hstat, pgStatDBHash);
+ 	while((hentry = hash_seq_search(&hstat)) != (long *)TRUE)
+ 	{
+ 		Oid		dbid;
+ 
+ 		if (hentry == NULL)
+ 			break;
+ 		
+ 		dbentry = (PgStat_StatDBEntry *)hentry;
+ 		dbid = dbentry->databaseid;
+ 
+ 		for (i = 0; i < dbidused; i++)
+ 		{
+ 			if (dbidlist[i] == dbid)
+ 			{
+ 				dbid = InvalidOid;
+ 				break;
+ 			}
+ 		}
+ 
+ 		if (dbid != InvalidOid)
+ 		{
+ 			nobjects++;
+ 			pgstat_drop_database(dbid);
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * Free the dbid list.
+ 	 */
+ 	pfree((char *)dbidlist);
+ 
+ 	/*
+ 	 * Tell the caller how many removeable objects we found
+ 	 */
+ 	return nobjects;
+ }
+ 
+ 
+ /* ----------
+  * pgstat_drop_database() -
+  *
+  *	Tell the collector that we just dropped a database.
+  *	This is the only message that shouldn't get lost in space. Otherwise
+  *	the collector will keep the statistics for the dead DB until his
+  *	stats file got removed while the postmaster is down.
+  * ----------
+  */
+ static void
+ pgstat_drop_database(Oid databaseid)
+ {
+ 	PgStat_MsgDropdb		msg;
+ 
+ 	if (pgStatSock < 0)
+ 		return;
+ 
+ 	msg.m_databaseid = databaseid;
+ 
+ 	pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_DROPDB);
+ 	pgstat_send(&msg, sizeof(msg));
+ }
+ 
+ 
+ /* ----------
+  * pgstat_reset_counters() -
+  *
+  *	Tell the statistics collector to reset counters for our database.
+  * ----------
+  */
+ void
+ pgstat_reset_counters(void)
+ {
+ 	PgStat_MsgResetcounter		msg;
+ 
+ 	if (pgStatSock < 0)
+ 		return;
+ 
+ 	if (!superuser())
+ 		elog(ERROR, "Only database superusers can reset statistic counters");
+ 
+ 	pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_RESETCOUNTER);
+ 	pgstat_send(&msg, sizeof(msg));
+ }
+ 
+ 
+ /* ----------
+  * pgstat_ping() -
+  *
+  *	Send some junk data to the collector to increase traffic.
+  * ----------
+  */
+ void
+ pgstat_ping(void)
+ {
+ 	PgStat_MsgDummy		msg;
+ 
+ 	if (pgStatSock < 0)
+ 		return;
+ 
+ 	pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_DUMMY);
+ 	pgstat_send(&msg, sizeof(msg));
+ }
+ 
+ 
+ /* ----------
+  * pgstat_initstats() -
+  *
+  *	Called from various places usually dealing with initialization
+  *	of Relation or Scan structures. The data placed into these
+  *	structures from here tell where later to count for buffer reads,
+  *	scans and tuples fetched.
+  * ----------
+  */
+ void
+ pgstat_initstats(PgStat_Info *stats, Relation rel)
+ {
+ 	PgStat_TableEntry	   *useent = NULL;
+ 	Oid						rel_id = rel->rd_id;
+ 	int						mb;
+ 	int						i;
+ 
+ 	/*
+ 	 * Initialize data not to count at all.
+ 	 */
+ 	stats->tabentry				= NULL;
+ 	stats->no_stats				= FALSE;
+ 	stats->heap_scan_counted	= FALSE;
+ 	stats->index_scan_counted	= FALSE;
+ 
+ 	if (pgStatSock < 0)
+ 	{
+ 		stats->no_stats = TRUE;
+ 		return;
+ 	}
+ 
+ 	/*
+ 	 * On the first of all calls initialize the message buffers.
+ 	 */
+ 	if (pgStatTabstatAlloc == 0)
+ 	{
+ 		pgStatTabstatAlloc = 4;
+ 		pgStatTabstatMessages = (PgStat_MsgTabstat **)
+ 					malloc(sizeof(PgStat_MsgTabstat *) * pgStatTabstatAlloc);
+ 		if (pgStatTabstatMessages == NULL)
+ 		{
+ 			perror("PGSTATBE: malloc(2)");
+ 			return;
+ 		}
+ 		for (i = 0; i < pgStatTabstatAlloc; i++)
+ 		{
+ 			pgStatTabstatMessages[i] = (PgStat_MsgTabstat *)
+ 					malloc(sizeof(PgStat_MsgTabstat));
+ 			if (pgStatTabstatMessages[i] == NULL)
+ 			{
+ 				perror("PGSTATBE: malloc(2)");
+ 				return;
+ 			}
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * Lookup the so far used table slots for this relation.
+ 	 */
+ 	for (mb = 0; mb < pgStatTabstatUsed; mb++)
+ 	{
+ 		for (i = 0; i < pgStatTabstatMessages[mb]->m_nentries; i++)
+ 		{
+ 			if (pgStatTabstatMessages[mb]->m_entry[i].t_id == rel_id)
+ 			{
+ 				stats->tabentry = (void *)&(pgStatTabstatMessages[mb]->m_entry[i]);
+ 				return;
+ 			}
+ 		}
+ 
+ 		if (pgStatTabstatMessages[mb]->m_nentries >= PGSTAT_NUM_TABENTRIES)
+ 			continue;
+ 		
+ 		/*
+ 		 * Not found, but found a message buffer with an empty slot
+ 		 * instead. Fine, let's use this one.
+ 		 */
+ 		i = pgStatTabstatMessages[mb]->m_nentries++;
+ 		useent = &pgStatTabstatMessages[mb]->m_entry[i];
+ 		memset(useent, 0, sizeof(PgStat_TableEntry));
+ 		useent->t_id = rel_id;
+ 		stats->tabentry = (void *)useent;
+ 		return;
+ 	}
+ 
+ 	/*
+ 	 * If we ran out of message buffers, we just allocate more.
+ 	 */
+ 	if (pgStatTabstatUsed >= pgStatTabstatAlloc)
+ 	{
+ 		pgStatTabstatAlloc += 4;
+ 		pgStatTabstatMessages = (PgStat_MsgTabstat **)
+ 					realloc(pgStatTabstatMessages,
+ 					sizeof(PgStat_MsgTabstat *) * pgStatTabstatAlloc);
+ 		if (pgStatTabstatMessages == NULL)
+ 		{
+ 			pgStatTabstatAlloc -= 4;
+ 			perror("PGSTATBE: malloc(2)");
+ 			return;
+ 		}
+ 		for (i = pgStatTabstatUsed; i < pgStatTabstatAlloc; i++)
+ 		{
+ 			pgStatTabstatMessages[i] = (PgStat_MsgTabstat *)
+ 					malloc(sizeof(PgStat_MsgTabstat));
+ 			if (pgStatTabstatMessages[i] == NULL)
+ 			{
+ 				pgStatTabstatAlloc -= 4;
+ 				perror("PGSTATBE: malloc(2)");
+ 				return;
+ 			}
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * Use the first entry of the next message buffer.
+ 	 */
+ 	mb = pgStatTabstatUsed++;
+ 	pgStatTabstatMessages[mb]->m_nentries = 1;
+ 	useent = &pgStatTabstatMessages[mb]->m_entry[0];
+ 	memset(useent, 0, sizeof(PgStat_TableEntry));
+ 	useent->t_id = rel_id;
+ 	stats->tabentry = (void *)useent;
+ 	return;
+ }
+ 
+ 
+ /* ----------
+  * pgstat_reset_heap_scan() -
+  *
+  *	Called from heap_rescan() to reset the heap_scan_counted flag.
+  *	Since the optimizer usually does a beginscan()/endscan() without
+  *	really doing a scan, we cannot count those calls. We have to wait
+  *	if after a beginscan() or rescan() really a call to the getnext()
+  *	function happens.
+  * ----------
+  */
+ void
+ pgstat_reset_heap_scan(PgStat_Info *stats)
+ {
+ 	if (stats->tabentry == NULL)
+ 		return;
+ 
+ 	stats->heap_scan_counted = FALSE;
+ }
+ 
+ 
+ /* ----------
+  * pgstat_count_heap_scan() -
+  *
+  *	Called from heap_getnext() to tell us that now the relation
+  *	really is scanned.
+  * ----------
+  */
+ void
+ pgstat_count_heap_scan(PgStat_Info *stats)
+ {
+ 	if (stats->tabentry == NULL)
+ 		return;
+ 
+ 	if (!stats->heap_scan_counted)
+ 	{
+ 		((PgStat_TableEntry *)(stats->tabentry))->t_numscans++;
+ 		stats->heap_scan_counted = TRUE;
+ 	}
+ }
+ 
+ 
+ /* ----------
+  * pgstat_count_heap_getnext() -
+  *
+  *	Called from heap_getnext() whenever a valid tuple is returned
+  *	from a sequential scan. The above cannot get combined into this,
+  *	because if a heap scan didn't return any tuples, the scan itself
+  *	would be missing in the stats.
+  * ----------
+  */
+ void
+ pgstat_count_heap_getnext(PgStat_Info *stats)
+ {
+ 	if (stats->tabentry == NULL)
+ 		return;
+ 
+ 	((PgStat_TableEntry *)(stats->tabentry))->t_tuples_returned++;
+ }
+ 
+ 
+ /* ----------
+  * pgstat_count_heap_fetch() -
+  *
+  *	Called from heap_fetch() if this is caused by a heap lookup
+  *	for an actually done index scan.
+  * ----------
+  */
+ void
+ pgstat_count_heap_fetch(PgStat_Info *stats)
+ {
+ 	if (stats->tabentry == NULL)
+ 		return;
+ 
+ 	((PgStat_TableEntry *)(stats->tabentry))->t_tuples_fetched++;
+ }
+ 
+ 
+ /* ----------
+  * pgstat_count_heap_insert() -
+  *
+  *	Called from heap_insert().
+  * ----------
+  */
+ void
+ pgstat_count_heap_insert(PgStat_Info *stats)
+ {
+ 	if (stats->tabentry == NULL)
+ 		return;
+ 
+ 	((PgStat_TableEntry *)(stats->tabentry))->t_tuples_inserted++;
+ }
+ 
+ 
+ /* ----------
+  * pgstat_count_heap_update() -
+  *
+  *	Called from heap_update().
+  * ----------
+  */
+ void
+ pgstat_count_heap_update(PgStat_Info *stats)
+ {
+ 	if (stats->tabentry == NULL)
+ 		return;
+ 
+ 	((PgStat_TableEntry *)(stats->tabentry))->t_tuples_updated++;
+ }
+ 
+ 
+ /* ----------
+  * pgstat_count_heap_delete() -
+  *
+  *	Called from heap_delete().
+  * ----------
+  */
+ void
+ pgstat_count_heap_delete(PgStat_Info *stats)
+ {
+ 	if (stats->tabentry == NULL)
+ 		return;
+ 
+ 	((PgStat_TableEntry *)(stats->tabentry))->t_tuples_deleted++;
+ }
+ 
+ 
+ /* ----------
+  * pgstat_reset_index_scan() -
+  *
+  *	See pgstat_reset_heap_scan().
+  * ----------
+  */
+ void
+ pgstat_reset_index_scan(PgStat_Info *stats)
+ {
+ 	if (stats->tabentry == NULL)
+ 		return;
+ 
+ 	stats->index_scan_counted = FALSE;
+ }
+ 
+ 
+ /* ----------
+  * pgstat_count_index_scan() -
+  *
+  *	See pgstat_count_heap_scan().
+  * ----------
+  */
+ void
+ pgstat_count_index_scan(PgStat_Info *stats)
+ {
+ 	if (stats->tabentry == NULL)
+ 		return;
+ 
+ 	if (!stats->index_scan_counted)
+ 	{
+ 		((PgStat_TableEntry *)(stats->tabentry))->t_numscans++;
+ 		stats->index_scan_counted = TRUE;
+ 	}
+ }
+ 
+ 
+ /* ----------
+  * pgstat_reset_index_getnext() -
+  *
+  *	See pgstat_count_heap_getnext().
+  * ----------
+  */
+ void
+ pgstat_count_index_getnext(PgStat_Info *stats)
+ {
+ 	if (stats->tabentry == NULL)
+ 		return;
+ 
+ 	((PgStat_TableEntry *)(stats->tabentry))->t_tuples_returned++;
+ }
+ 
+ 
+ /* ----------
+  * pgstat_count_buffer_read() -
+  *
+  *	Called from bufmgr.c when a buffer is looked up in the shared buffer
+  *	cache. The real number of buffers read from the disk (or at least the
+  *	OSs or drives cache) is this minus buffer_hit_count below.
+  * ----------
+  */
+ void
+ pgstat_count_buffer_read(PgStat_Info *stats, Relation rel)
+ {
+ 	if (stats->tabentry == NULL)
+ 	{
+ 		if (stats->no_stats)
+ 			return;
+ 		pgstat_initstats(stats, rel);
+ 		if (stats->tabentry == NULL)
+ 			return;
+ 	}
+ 
+ 	((PgStat_TableEntry *)(stats->tabentry))->t_blocks_fetched++;
+ }
+ 
+ 
+ /* ----------
+  * pgstat_count_buffer_hit() -
+  *
+  *	Counts how many buffer per relation (or index) have been found
+  *	in the buffer cache.
+  * ----------
+  */
+ void
+ pgstat_count_buffer_hit(PgStat_Info *stats, Relation rel)
+ {
+ 	if (stats->tabentry == NULL)
+ 	{
+ 		if (stats->no_stats)
+ 			return;
+ 		pgstat_initstats(stats, rel);
+ 		if (stats->tabentry == NULL)
+ 			return;
+ 	}
+ 
+ 	((PgStat_TableEntry *)(stats->tabentry))->t_blocks_hit++;
+ }
+ 
+ 
+ /* ----------
+  * pgstat_count_xact_commit() -
+  *
+  *	Called from access/transam/xact.c to count transaction commits.
+  * ----------
+  */
+ void
+ pgstat_count_xact_commit(void)
+ {
+ 	pgStatXactCommit++;
+ 
+ 	/*
+ 	 * If there was no relation activity yet, just make
+ 	 * one existing message buffer used without slots,
+ 	 * causing the next report to tell new xact-counters.
+ 	 */
+ 	if (pgStatTabstatAlloc > 0)
+ 	{
+ 		if (pgStatTabstatUsed == 0)
+ 		{
+ 			pgStatTabstatUsed++;
+ 			pgStatTabstatMessages[0]->m_nentries = 0;
+ 		}
+ 	}
+ }
+ 
+ 
+ /* ----------
+  * pgstat_count_xact_rollback() -
+  *
+  *	Called from access/transam/xact.c to count transaction rollbacks.
+  * ----------
+  */
+ void
+ pgstat_count_xact_rollback(void)
+ {
+ 	pgStatXactRollback++;
+ 
+ 	/*
+ 	 * If there was no relation activity yet, just make
+ 	 * one existing message buffer used without slots,
+ 	 * causing the next report to tell new xact-counters.
+ 	 */
+ 	if (pgStatTabstatAlloc > 0)
+ 	{
+ 		if (pgStatTabstatUsed == 0)
+ 		{
+ 			pgStatTabstatUsed++;
+ 			pgStatTabstatMessages[0]->m_nentries = 0;
+ 		}
+ 	}
+ }
+ 
+ 
+ /* ----------
+  * pgstat_fetch_stat_dbentry() -
+  *
+  *	Support function for the SQL-callable pgstat* functions. Returns
+  *	the collected statistics for one database or NULL. NULL doesn't mean
+  *	that the database doesn't exist, it is just not yet known by the
+  *	collector, so the caller is better off to report ZERO instead.
+  * ----------
+  */
+ PgStat_StatDBEntry *
+ pgstat_fetch_stat_dbentry(Oid dbid)
+ {
+ 	PgStat_StatDBEntry	   *dbentry;
+ 	bool					found;
+ 
+ 	/*
+ 	 * If not done for this transaction, read the statistics collector
+ 	 * stats file into some hash tables. Be careful with the read_statsfile()
+ 	 * call below!
+ 	 */
+ 	if (pgStatDBHashXact != GetCurrentTransactionId())
+ 	{
+ 		pgstat_read_statsfile(&pgStatDBHash, MyDatabaseId, 
+ 						&pgStatBeTable, &pgStatNumBackends);
+ 		pgStatDBHashXact = GetCurrentTransactionId();
+ 	}
+ 
+ 	/*
+ 	 * Lookup the requested database
+ 	 */
+ 	dbentry = (PgStat_StatDBEntry *)hash_search(pgStatDBHash,
+ 					(char *)&dbid, HASH_FIND, &found);
+ 	if (!found || dbentry == NULL)
+ 		return NULL;
+ 
+ 	return dbentry;
+ }
+ 
+ 
+ /* ----------
+  * pgstat_fetch_stat_tabentry() -
+  *
+  *	Support function for the SQL-callable pgstat* functions. Returns
+  *	the collected statistics for one table or NULL. NULL doesn't mean
+  *	that the table doesn't exist, it is just not yet known by the
+  *	collector, so the caller is better off to report ZERO instead.
+  * ----------
+  */
+ PgStat_StatTabEntry *
+ pgstat_fetch_stat_tabentry(Oid relid)
+ {
+ 	PgStat_StatDBEntry	   *dbentry;
+ 	PgStat_StatTabEntry	   *tabentry;
+ 	bool					found;
+ 
+ 	/*
+ 	 * If not done for this transaction, read the statistics collector
+ 	 * stats file into some hash tables. Be careful with the read_statsfile()
+ 	 * call below!
+ 	 */
+ 	if (pgStatDBHashXact != GetCurrentTransactionId())
+ 	{
+ 		pgstat_read_statsfile(&pgStatDBHash, MyDatabaseId, 
+ 						&pgStatBeTable, &pgStatNumBackends);
+ 		pgStatDBHashXact = GetCurrentTransactionId();
+ 	}
+ 
+ 	/*
+ 	 * Lookup our database.
+ 	 */
+ 	dbentry = (PgStat_StatDBEntry *)hash_search(pgStatDBHash,
+ 					(char *)&MyDatabaseId, HASH_FIND, &found);
+ 	if (!found || dbentry == NULL)
+ 		return NULL;
+ 
+ 	/*
+ 	 * Now inside the DB's table hash table lookup the requested one.
+ 	 */
+ 	if (dbentry->tables == NULL)
+ 		return NULL;
+ 	tabentry = (PgStat_StatTabEntry *)hash_search(dbentry->tables,
+ 					(char *)&relid, HASH_FIND, &found);
+ 	if (!found || tabentry == NULL)
+ 		return NULL;
+ 
+ 	return tabentry;
+ }
+ 
+ 
+ /* ----------
+  * pgstat_fetch_stat_beentry() -
+  *
+  *	Support function for the SQL-callable pgstat* functions. Returns
+  *	the actual activity slot of one active backend. The caller is
+  *	responsible for a check if the actual user is permitted to see
+  *	that info (especially the querystring).
+  * ----------
+  */
+ PgStat_StatBeEntry *
+ pgstat_fetch_stat_beentry(int beid)
+ {
+ 	if (pgStatDBHashXact != GetCurrentTransactionId())
+ 	{
+ 		pgstat_read_statsfile(&pgStatDBHash, MyDatabaseId, 
+ 						&pgStatBeTable, &pgStatNumBackends);
+ 		pgStatDBHashXact = GetCurrentTransactionId();
+ 	}
+ 
+ 	if (beid < 1 || beid > pgStatNumBackends)
+ 		return NULL;
+ 
+ 	return &pgStatBeTable[beid - 1];
+ }
+ 
+ 
+ /* ----------
+  * pgstat_fetch_stat_numbackends() -
+  *
+  *	Support function for the SQL-callable pgstat* functions. Returns
+  *	the maximum current backend id.
+  * ----------
+  */
+ int
+ pgstat_fetch_stat_numbackends(void)
+ {
+ 	if (pgStatDBHashXact != GetCurrentTransactionId())
+ 	{
+ 		pgstat_read_statsfile(&pgStatDBHash, MyDatabaseId, 
+ 						&pgStatBeTable, &pgStatNumBackends);
+ 		pgStatDBHashXact = GetCurrentTransactionId();
+ 	}
+ 
+ 	return pgStatNumBackends;
+ }
+ 
+ 
+ 
+ 
+ 
+ 
+ 
+ 
+ 
+ 
+ 
+ 
+ /* ------------------------------------------------------------
+  * Local support functions follow
+  * ------------------------------------------------------------
+  */
+ 
+ 
+ /* ----------
+  * pgstat_setheader() -
+  *
+  *		Set common header fields in a statistics message
+  * ----------
+  */
+ static void
+ pgstat_setheader(PgStat_MsgHdr *hdr, int mtype)
+ {
+ 	hdr->m_type			= mtype;
+ 	hdr->m_backendid	= MyBackendId;
+ 	hdr->m_procpid		= MyProcPid;
+ 	hdr->m_databaseid	= MyDatabaseId;
+ 	hdr->m_userid		= GetSessionUserId();
+ }
+ 
+ 
+ /* ----------
+  * pgstat_send() -
+  *
+  *		Send out one statistics message to the collector
+  * ----------
+  */
+ static void
+ pgstat_send(void *msg, int len)
+ {
+ 	if (pgStatSock < 0)
+ 		return;
+ 
+ 	((PgStat_MsgHdr *)msg)->m_size = len;
+ 
+ 	sendto(pgStatSock, msg, len, 0, 
+ 			(struct sockaddr *)&pgStatAddr, sizeof(pgStatAddr));
+ }
+ 
+ 
+ /* ------------------------------------------------------------
+  * Local functions implementing the statistics collector itself follow
+  *------------------------------------------------------------ 
+  */
+ 
+ 
+ /* ----------
+  * pgstat_main() -
+  *
+  *	The statistics collector itself.
+  * ----------
+  */
+ static void
+ pgstat_main(void)
+ {
+ 	PgStat_Msg	msg;
+ 	fd_set			rfds;
+ 	int				maxfd;
+ 	int				nready;
+ 	int				len;
+ 	int				dlen;
+ 	struct timeval	timeout;
+ 	struct timeval	next_statwrite;
+ 	bool			need_statwrite;
+ 	HASHCTL			hash_ctl;
+ 
+ 	/*
+ 	 * Close the writing end of the postmaster pipe, so we'll see
+ 	 * it closing when the postmaster terminates and can terminate
+ 	 * as well.
+ 	 */
+ 	close(pgStatPmPipe[1]);
+ 
+ 	/*
+ 	 * Ignore all signals usually bound to some action in the postmaster
+ 	 */
+ 	signal(SIGHUP, SIG_IGN);
+ 	signal(SIGINT, SIG_IGN);
+ 	signal(SIGQUIT, SIG_IGN);
+ 	signal(SIGTERM, SIG_IGN);
+ 	signal(SIGALRM, SIG_IGN);
+ 	signal(SIGPIPE, SIG_IGN);
+ 	signal(SIGUSR1, SIG_IGN);
+ 	signal(SIGUSR2, SIG_IGN);
+ 	signal(SIGCHLD, SIG_IGN);
+ 	signal(SIGTTIN, SIG_IGN);
+ 	signal(SIGTTOU, SIG_IGN);
+ 	signal(SIGWINCH, SIG_IGN);
+ 
+ 	/*
+ 	 * Write the initial status file right at startup
+ 	 */
+ 	gettimeofday(&next_statwrite, NULL);
+ 	need_statwrite = TRUE;
+ 
+ 	/*
+ 	 * Now we start the buffer process to read from the socket, so
+ 	 * we have a little more time to process incoming messages.
+ 	 */
+ 	if (pipe(pgStatPipe) < 0)
+ 	{
+ 		perror("PGSTAT: pipe(2)");
+ 		exit(1);
+ 	}
+ 
+ 	switch(fork())
+ 	{
+ 		case -1:	perror("PGSTAT: fork(2)");
+ 					exit(1);
+ 
+ 		case 0:		close(pgStatPipe[0]);
+ 					signal(SIGPIPE, SIG_DFL);
+ 					pgstat_recvbuffer();
+ 					exit(2);
+ 
+ 		default:	close(pgStatPipe[1]);
+ 					close(pgStatSock);
+ 					break;
+ 	}
+ 
+ 	/*
+ 	 * Read in an existing statistics stats file or initialize the
+ 	 * stats to zero.
+ 	 */
+ 	pgStatRunningInCollector = TRUE;
+ 	pgstat_read_statsfile(&pgStatDBHash, InvalidOid, NULL, NULL);
+ 
+ 	/*
+ 	 * Create the dead backend hashtable
+ 	 */
+ 	memset(&hash_ctl, 0, sizeof(hash_ctl));
+ 	hash_ctl.keysize  = sizeof(int);
+ 	hash_ctl.datasize = sizeof(PgStat_StatBeDead);
+ 	hash_ctl.hash     = tag_hash;
+ 	pgStatBeDead = hash_create(PGSTAT_BE_HASH_SIZE, &hash_ctl, 
+ 							HASH_ELEM | HASH_FUNCTION);
+ 	if (pgStatBeDead == NULL)
+ 	{
+ 		fprintf(stderr, 
+ 				"PGSTAT: Creation of dead backend hash table failed\n");
+ 		exit(1);
+ 	}
+ 
+ 	/*
+ 	 * Create the known backends table
+ 	 */
+ 	pgStatBeTable = (PgStat_StatBeEntry *)malloc(
+ 					sizeof(PgStat_StatBeEntry) * MaxBackends);
+ 	if (pgStatBeTable == NULL)
+ 	{
+ 		perror("PGSTAT: Allocation of backend table failed");
+ 		exit(1);
+ 	}
+ 	memset(pgStatBeTable, 0, sizeof(PgStat_StatBeEntry) * MaxBackends);
+ 
+ 	/*
+ 	 * Process incoming messages and handle all the reporting stuff
+ 	 * until the postmaster waves us good bye.
+ 	 */
+ 	for (;;)
+ 	{
+ 		/*
+ 		 * If we need to write the status file again (there have
+ 		 * been changes in the statistics since we wrote it last)
+ 		 * calculate the timeout until we have to do so.
+ 		 */
+ 		if (need_statwrite)
+ 		{
+ 			gettimeofday(&timeout, NULL);
+ 			timeout.tv_usec = next_statwrite.tv_usec - timeout.tv_usec;
+ 			timeout.tv_sec  = next_statwrite.tv_sec  - timeout.tv_sec;
+ 			if (timeout.tv_usec < 0)
+ 			{
+ 				timeout.tv_sec  -= 1;
+ 				timeout.tv_usec += 1000000;
+ 			}
+ 			if (timeout.tv_sec < 0)
+ 			{
+ 				timeout.tv_sec  = 0;
+ 				timeout.tv_usec = 0;
+ 			}
+ 		}
+ 
+ 		/*
+ 		 * Setup the descriptor set for select(2)
+ 		 */
+ 		FD_ZERO(&rfds);
+ 		FD_SET(pgStatPipe[0], &rfds);
+ 		FD_SET(pgStatPmPipe[0], &rfds);
+ 
+ 		if (pgStatPipe[0] > pgStatPmPipe[0])
+ 			maxfd = pgStatPipe[0];
+ 		else
+ 			maxfd = pgStatPmPipe[0];
+ 
+ 		/*
+ 		 * Now wait for something to do.
+ 		 */
+ 		nready = select(maxfd + 1, &rfds, NULL, NULL, 
+ 						(need_statwrite) ? &timeout : NULL);
+ 		if (nready < 0)
+ 		{
+ 			perror("PGSTAT: select(2)");
+ 			exit(1);
+ 		}
+ 
+ 		/*
+ 		 * If there are no descriptors ready, our timeout for writing
+ 		 * the stats file happened.
+ 		 */
+ 		if (nready == 0)
+ 		{
+ 			pgstat_write_statsfile();
+ 			need_statwrite = FALSE;
+ 
+ 			continue;
+ 		}
+ 
+ 		/*
+ 		 * Check if there is a new statistics message to collect.
+ 		 */
+ 		if (FD_ISSET(pgStatPipe[0], &rfds))
+ 		{
+ 			/*
+ 			 * If this is the first message after we wrote the stats
+ 			 * file the last time, setup the timeout that it'd be
+ 			 * written.
+ 			 */
+ 			if (!need_statwrite)
+ 			{
+ 				gettimeofday(&next_statwrite, NULL);
+ 				next_statwrite.tv_usec += ((PGSTAT_STAT_INTERVAL) * 1000);
+ 				next_statwrite.tv_sec  += (next_statwrite.tv_usec / 1000000);
+ 				next_statwrite.tv_usec %= 1000000;
+ 				need_statwrite = TRUE;
+ 			}
+ 
+ 			/*
+ 			 * Read the header.
+ 			 */
+ 			len = read(pgStatPipe[0], &msg, sizeof(PgStat_MsgHdr));
+ 			if (len < 0)
+ 			{
+ 				perror("PGSTAT: read(2)");
+ 				exit(1);
+ 			}
+ 			if (len == 0)
+ 			{
+ 				return;
+ 			}
+ 			if (len != sizeof(PgStat_MsgHdr))
+ 			{
+ 				fprintf(stderr, "PGSTAT: short read(2)");
+ 				exit(1);
+ 			}
+ 
+ 			/*
+ 			 * And the body. We need to do it in two steps because
+ 			 * we don't know the length.
+ 			 */
+ 			dlen = msg.msg_hdr.m_size - sizeof(PgStat_MsgHdr);
+ 			if (dlen > 0)
+ 			{
+ 				len = read(pgStatPipe[0], 
+ 						((char *)&msg) + sizeof(PgStat_MsgHdr), dlen);
+ 				if (len < 0)
+ 				{
+ 					perror("PGSTAT: read(2)");
+ 					exit(1);
+ 				}
+ 				if (len == 0)
+ 				{
+ 					return;
+ 				}
+ 				if (len != dlen)
+ 				{
+ 					fprintf(stderr, "PGSTAT: short read(2)");
+ 					exit(1);
+ 				}
+ 			}
+ 
+ 			/*
+ 			 * Distribute the message to the specific function
+ 			 * handling it.
+ 			 */
+ 			len += sizeof(PgStat_MsgHdr);
+ 			switch (msg.msg_hdr.m_type)
+ 			{
+ 				case PGSTAT_MTYPE_DUMMY:
+ 					break;
+ 
+ 				case PGSTAT_MTYPE_BESTART:
+ 					pgstat_recv_bestart((PgStat_MsgBestart *)&msg, len);
+ 					break;
+ 
+ 				case PGSTAT_MTYPE_BETERM:
+ 					pgstat_recv_beterm((PgStat_MsgBeterm *)&msg, len);
+ 					break;
+ 
+ 				case PGSTAT_MTYPE_TABSTAT:
+ 					pgstat_recv_tabstat((PgStat_MsgTabstat *)&msg, len);
+ 					break;
+ 
+ 				case PGSTAT_MTYPE_TABPURGE:
+ 					pgstat_recv_tabpurge((PgStat_MsgTabpurge *)&msg, len);
+ 					break;
+ 
+ 				case PGSTAT_MTYPE_ACTIVITY:
+ 					pgstat_recv_activity((PgStat_MsgActivity *)&msg, len);
+ 					break;
+ 
+ 				case PGSTAT_MTYPE_DROPDB:
+ 					pgstat_recv_dropdb((PgStat_MsgDropdb *)&msg, len);
+ 					break;
+ 
+ 				case PGSTAT_MTYPE_RESETCOUNTER:
+ 					pgstat_recv_resetcounter((PgStat_MsgResetcounter *)&msg,
+ 												len);
+ 					break;
+ 
+ 				default:
+ 					break;
+ 			}
+ 
+ 			/*
+ 			 * Globally count messages and start over.
+ 			 */
+ 			pgStatNumMessages++;
+ 			continue;
+ 		}
+ 
+ 		/*
+ 		 * If the postmaster pipe is ready for reading this means that
+ 		 * the kernel must have closed it because of the termination
+ 		 * of the postmaster (he never really writes to it). Give up
+ 		 * then, but save the final stats in case we want to reuse
+ 		 * them at startup in the future.
+ 		 */
+ 		if (FD_ISSET(pgStatPmPipe[0], &rfds))
+ 		{
+ 			pgstat_write_statsfile();
+ 			return;
+ 		}
+ 	}
+ }
+ 
+ 
+ /* ----------
+  * pgstat_recvbuffer() -
+  *
+  *	This is a special receive buffer started by the statistics
+  *	collector itself and running in a separate process. It's only
+  *	purpose is to receive messages from the UDP socket as fast as
+  *	possible and forward them over a pipe into the collector
+  *	itself.
+  * ----------
+  */
+ static void
+ pgstat_recvbuffer(void)
+ {
+ 	fd_set				rfds;
+ 	fd_set				wfds;
+ 	int					maxfd;
+ 	int					nready;
+ 	int					len;
+ 	PgStat_Msg		   *msgbuffer = NULL;
+ 	int					msg_recv = 0;
+ 	int					msg_send = 0;
+ 	int					msg_have = 0;
+ 	struct sockaddr_in	fromaddr;
+ 	int					fromlen;
+ 	int					overflow = 0;
+ 
+ 	/*
+ 	 * Allocate the message buffer
+ 	 */
+ 	msgbuffer = (PgStat_Msg *)malloc(sizeof(PgStat_Msg) *
+ 				PGSTAT_RECVBUFFERSZ);
+ 	if (msgbuffer == NULL)
+ 	{
+ 		perror("PGSTATBUFF: malloc()");
+ 		exit(1);
+ 	}
+ 
+ 	/*
+ 	 * Loop forever
+ 	 */
+ 	for (;;)
+ 	{
+ 		FD_ZERO(&rfds);
+ 		FD_ZERO(&wfds);
+ 		maxfd = -1;
+ 
+ 		/*
+ 		 * As long as we have buffer space we add the socket
+ 		 * to the read descriptor set.
+ 		 */
+ 		if (msg_have < PGSTAT_RECVBUFFERSZ)
+ 		{
+ 			FD_SET(pgStatSock, &rfds);
+ 			maxfd = pgStatSock;
+ 			overflow = 0;
+ 		}
+ 		else
+ 		{
+ 			if (overflow == 0)
+ 			{
+ 				fprintf(stderr, "PGSTAT: Warning - receive buffer full\n");
+ 				overflow = 1;
+ 			}
+ 		}
+ 
+ 
+ 		/*
+ 		 * If we have messages to write out, we add the pipe
+ 		 * to the write descriptor set. Otherwise, we check if
+ 		 * the postmaster might have terminated.
+ 		 */
+ 		if (msg_have > 0)
+ 		{
+ 			FD_SET(pgStatPipe[1], &wfds);
+ 			if (pgStatPipe[1] > maxfd)
+ 				maxfd = pgStatPipe[1];
+ 		}
+ 		else
+ 		{
+ 			FD_SET(pgStatPmPipe[0], &rfds);
+ 			if (pgStatPmPipe[0] > maxfd)
+ 				maxfd = pgStatPmPipe[0];
+ 		}
+ 
+ 
+ 		/*
+ 		 * Wait for some work to do.
+ 		 */
+ 		nready = select(maxfd + 1, &rfds, &wfds, NULL, NULL);
+ 		if (nready < 0)
+ 		{
+ 			perror("PGSTATBUFF: select(2)");
+ 			exit(1);
+ 		}
+ 
+ 		/*
+ 		 * If there is a message on the socket, read it and check for
+ 		 * validity.
+ 		 */
+ 		if (FD_ISSET(pgStatSock, &rfds))
+ 		{
+ 			fromlen = sizeof(fromaddr);
+ 			len = recvfrom(pgStatSock, 
+ 						&msgbuffer[msg_recv], sizeof(PgStat_Msg), 0,
+ 						(struct sockaddr *)&fromaddr, &fromlen);
+ 			if (len < 0)
+ 			{
+ 				perror("PGSTATBUFF: recvfrom(2)");
+ 				exit(1);
+ 			}
+ 
+ 			/*
+ 			 * We ignore messages that are smaller than our common header
+ 			 */
+ 			if (len < sizeof(PgStat_MsgHdr))
+ 				continue;
+ 
+ 			/*
+ 			 * The received length must match the length in the header
+ 			 */
+ 			if (msgbuffer[msg_recv].msg_hdr.m_size != len)
+ 				continue;
+ 
+ 			/*
+ 			 * The source address of the packet must be our own socket.
+ 			 * This ensures that only real hackers or our own backends
+ 			 * tell us something.
+ 			 */
+ 			if (fromaddr.sin_addr.s_addr != pgStatAddr.sin_addr.s_addr)
+ 				continue;
+ 			if (fromaddr.sin_port != pgStatAddr.sin_port)
+ 				continue;
+ 			
+ 			/*
+ 			 * O.K. - we accept this message.
+ 			 */
+ 			msg_have++;
+ 			msg_recv++;
+ 			if (msg_recv == PGSTAT_RECVBUFFERSZ)
+ 				msg_recv = 0;
+ 		}
+ 
+ 		/*
+ 		 * If the collector is ready to receive, write a buffered
+ 		 * message into his pipe.
+ 		 */
+ 		if (FD_ISSET(pgStatPipe[1], &wfds))
+ 		{
+ 			len = write(pgStatPipe[1], &msgbuffer[msg_send], 
+ 						msgbuffer[msg_send].msg_hdr.m_size);
+ 			if (len < 0)
+ 			{
+ 				perror("PGSTATBUFF: write(2)");
+ 				exit(1);
+ 			}
+ 			if (len != msgbuffer[msg_send].msg_hdr.m_size)
+ 			{
+ 				fprintf(stderr, "PGSTATBUFF: short write(2)");
+ 				exit(1);
+ 			}
+ 
+ 			msg_have--;
+ 			msg_send++;
+ 			if (msg_send == PGSTAT_RECVBUFFERSZ)
+ 				msg_send = 0;
+ 		}
+ 
+ 		/*
+ 		 * Make sure we forwarded all messages before we check for
+ 		 * Postmaster termination.
+ 		 */
+ 		if (FD_ISSET(pgStatSock, &rfds) || FD_ISSET(pgStatPipe[1], &wfds))
+ 			continue;
+ 
+ 		/*
+ 		 * If the pipe from the postmaster is ready for reading,
+ 		 * the kernel must have closed it on exit() (the postmaster
+ 		 * never really writes to it). So we've done our job.
+ 		 */
+ 		if (FD_ISSET(pgStatPmPipe[0], &rfds))
+ 			exit(0);
+ 	}
+ }
+ 
+ 
+ /* ----------
+  * pgstat_add_backend() -
+  *
+  *	Support function to keep our backen list up to date.
+  * ----------
+  */
+ static int
+ pgstat_add_backend(PgStat_MsgHdr *msg)
+ {
+ 	PgStat_StatDBEntry	   *dbentry;
+ 	PgStat_StatBeEntry	   *beentry;
+ 	PgStat_StatBeDead		   *deadbe;
+ 	bool						found;
+ 
+ 	/*
+ 	 * Check that the backend ID is valid
+ 	 */
+ 	if (msg->m_backendid < 1 || msg->m_backendid > MaxBackends)
+ 	{
+ 		fprintf(stderr, "PGSTAT: Invalid backend ID %d\n", msg->m_backendid);
+ 		return -1;
+ 	}
+ 
+ 	/*
+ 	 * Get the slot for this backendid.
+ 	 */
+ 	beentry = &pgStatBeTable[msg->m_backendid - 1];
+ 	if (beentry->databaseid != InvalidOid)
+ 	{
+ 		/*
+ 		 * If the slot contains the PID of this backend, 
+ 		 * everything is fine and we got nothing to do.
+ 		 */
+ 		if (beentry->procpid == msg->m_procpid)
+ 			return 0;
+ 	}
+ 
+ 	/*
+ 	 * Lookup if this backend is known to be dead. This can be caused
+ 	 * due to messages arriving in the wrong order - i.e. Postmaster's
+ 	 * BETERM message might have arrived before we received all the
+ 	 * backends stats messages, or even a new backend with the same
+ 	 * backendid was faster in sending his BESTART.
+ 	 *
+ 	 * If the backend is known to be dead, we ignore this add.
+ 	 */
+ 	deadbe = (PgStat_StatBeDead *)hash_search(pgStatBeDead,
+ 						(char *)&(msg->m_procpid), HASH_FIND, &found);
+ 	if (deadbe == NULL)
+ 	{
+ 		fprintf(stderr, "PGSTAT: Dead backend table corrupted - abort\n");
+ 		exit(1);
+ 	}
+ 	if (found)
+ 		return 1;
+ 
+ 	/*
+ 	 * Backend isn't known to be dead. If it's slot is currently used,
+ 	 * we have to kick out the old backend.
+ 	 */
+ 	if (beentry->databaseid != InvalidOid)
+ 		pgstat_sub_backend(beentry->procpid);
+ 
+ 	/*
+ 	 * Put this new backend into the slot.
+ 	 */
+ 	beentry->databaseid = msg->m_databaseid;
+ 	beentry->procpid    = msg->m_procpid;
+ 	beentry->userid		= msg->m_userid;
+ 
+ 	/*
+ 	 * Lookup or create the database entry for this backends DB.
+ 	 */
+ 	dbentry = (PgStat_StatDBEntry *)hash_search(pgStatDBHash,
+ 					(char *)&(msg->m_databaseid), HASH_ENTER,
+ 					&found);
+     if (dbentry == NULL)
+ 	{
+ 		fprintf(stderr, "PGSTAT: DB hash table corrupted - abort\n");
+ 		exit(1);
+ 	}
+ 
+ 	/*
+ 	 * If not found, initialize the new one.
+ 	 */
+ 	if (!found)
+ 	{
+ 		HASHCTL			hash_ctl;
+ 
+ 		dbentry->tables				= NULL;
+ 		dbentry->n_xact_commit		= 0;
+ 		dbentry->n_xact_rollback	= 0;
+ 		dbentry->n_blocks_fetched	= 0;
+ 		dbentry->n_blocks_hit		= 0;
+ 		dbentry->n_connects			= 0;
+ 		dbentry->destroy			= 0;
+ 
+ 		memset(&hash_ctl, 0, sizeof(hash_ctl));
+ 		hash_ctl.keysize  = sizeof(Oid);
+ 		hash_ctl.datasize = sizeof(PgStat_StatTabEntry);
+ 		hash_ctl.hash     = tag_hash;
+ 		dbentry->tables = hash_create(PGSTAT_TAB_HASH_SIZE, &hash_ctl,
+ 							HASH_ELEM | HASH_FUNCTION);
+ 		if (dbentry->tables == NULL)
+ 		{
+ 			fprintf(stderr, "PGSTAT: failed to initialize hash table for "
+ 							"new database entry\n");
+ 			exit(1);
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * Count number of connects to the database
+ 	 */
+ 	dbentry->n_connects++;
+ 
+ 	return 0;
+ }
+ 
+ 
+ /* ----------
+  * pgstat_sub_backend() -
+  *
+  *	Remove a backend from the actual backends list.
+  * ----------
+  */
+ static void
+ pgstat_sub_backend(int procpid)
+ {
+ 	int						i;
+ 	PgStat_StatBeDead	   *deadbe;
+ 	bool					found;
+ 
+ 	/*
+ 	 * Search in the known-backends table for the slot containing
+ 	 * this PID.
+ 	 */
+ 	for (i = 0; i < MaxBackends; i++)
+ 	{
+ 		if (pgStatBeTable[i].databaseid != InvalidOid &&
+ 			pgStatBeTable[i].procpid == procpid)
+ 		{
+ 			/*
+ 			 * That's him. Add an entry to the known to be dead backends.
+ 			 * Due to possible misorder in the arrival of UDP packets
+ 			 * it's possible that even if we know the backend is dead,
+ 			 * there could still be messages queued that arrive later.
+ 			 * Those messages must not cause our number of backends
+ 			 * statistics to get screwed up, so we remember for a couple
+ 			 * of seconds that this PID is dead and ignore them (only
+ 			 * the counting of backends, not the table access stats they
+ 			 * sent).
+ 			 */
+ 			deadbe = (PgStat_StatBeDead *)hash_search(pgStatBeDead,
+ 							(char *)&procpid, HASH_ENTER, &found);
+ 			if (deadbe == NULL)
+ 			{
+ 				fprintf(stderr, "PGSTAT: dead backend hash table corrupted "
+ 								"- abort\n");
+ 				exit(1);
+ 			}
+ 			if (!found)
+ 			{
+ 				deadbe->backendid = i + 1;
+ 				deadbe->destroy = PGSTAT_DESTROY_COUNT;
+ 			}
+ 
+ 			/*
+ 			 * Declare the backend slot empty.
+ 			 */
+ 			pgStatBeTable[i].databaseid = InvalidOid;
+ 			return;
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * No big problem if not found. This can happen if UDP messages
+ 	 * arrive out of order here.
+ 	 */
+ }
+ 
+ 
+ /* ----------
+  * pgstat_write_statsfile() -
+  *
+  *	Tell the news.
+  * ----------
+  */
+ static void
+ pgstat_write_statsfile(void)
+ {
+ 	HASH_SEQ_STATUS				hstat;
+ 	HASH_SEQ_STATUS				tstat;
+ 	PgStat_StatDBEntry		   *dbentry;
+ 	PgStat_StatTabEntry		   *tabentry;
+ 	PgStat_StatBeDead		   *deadbe;
+ 	FILE					   *fpout;
+ 	long					   *hentry;
+ 	bool						found;
+ 	int							i;
+ 
+ 	/*
+ 	 * Open the statistics temp file to write out
+ 	 * the current values.
+ 	 */
+ 	fpout = fopen(pgStat_tmpfname, "w");
+ 	if (fpout == NULL)
+ 	{
+ 		fprintf(stderr, "PGSTAT: cannot open temp stats file\nPGSTAT: ");
+ 		perror(pgStat_tmpfname);
+ 		fflush(stderr);
+ 		return;
+ 	}
+ 
+ 	/*
+ 	 * Walk through the database table.
+ 	 */
+ 	hash_seq_init(&hstat, pgStatDBHash);
+ 	while ((hentry = hash_seq_search(&hstat)) != (long *)TRUE)
+ 	{
+ 		if (hentry == NULL)
+ 		{
+ 			fprintf(stderr, "PGSTAT: database hash table corrupted "
+ 							"- abort\n");
+ 			exit(1);
+ 		}
+ 		dbentry = (PgStat_StatDBEntry *)hentry;
+ 
+ 		/*
+ 		 * If this database is marked destroyed, count down and do
+ 		 * so if it reaches 0.
+ 		 */
+ 		if (dbentry->destroy > 0)
+ 		{
+ 			if (--(dbentry->destroy) == 0)
+ 			{
+ 				if (dbentry->tables != NULL)
+ 					hash_destroy(dbentry->tables);
+ 
+ 				hentry = hash_search(pgStatDBHash, 
+ 								(char *)&(dbentry->databaseid),
+ 								HASH_REMOVE, &found);
+ 				if (hentry == NULL)
+ 				{
+ 					fprintf(stderr, "PGSTAT: database hash table corrupted "
+ 									"during cleanup - abort\n");
+ 					exit(1);
+ 				}
+ 			}
+ 			/*
+ 			 * Don't include statistics for it.
+ 			 */
+ 			continue;
+ 		}
+ 
+ 		/*
+ 		 * Write out the DB line including the number of life backends.
+ 		 */
+ 		fputc('D', fpout);
+ 		fwrite(dbentry, sizeof(PgStat_StatDBEntry), 1, fpout);
+ 
+ 		/*
+ 		 * Walk through the databases access stats per table.
+ 		 */
+ 		hash_seq_init(&tstat, dbentry->tables);
+ 		while((hentry = hash_seq_search(&tstat)) != (long *)TRUE)
+ 		{
+ 			if (hentry == NULL)
+ 			{
+ 				fprintf(stderr, "PGSTAT: tables hash table for database "
+ 								"%d corrupted - abort\n",
+ 								dbentry->databaseid);
+ 				exit(1);
+ 			}
+ 			tabentry = (PgStat_StatTabEntry *)hentry;
+ 
+ 			/*
+ 			 * If table entry marked for destruction, same as above
+ 			 * for the database entry.
+ 			 */
+ 			if (tabentry->destroy > 0)
+ 			{
+ 				if (--(tabentry->destroy) == 0)
+ 				{
+ 					hentry = hash_search(dbentry->tables,
+ 									(char *)&(tabentry->tableid),
+ 									HASH_REMOVE, &found);
+ 					if (hentry == NULL)
+ 					{
+ 						fprintf(stderr, "PGSTAT: tables hash table for "
+ 										"database %d corrupted during "
+ 										"cleanup - abort\n",
+ 										dbentry->databaseid);
+ 						exit(1);
+ 					}
+ 				}
+ 				continue;
+ 			}
+ 
+ 			/*
+ 			 * At least we think this is still a life table. Print
+ 			 * it's access stats.
+ 			 */
+ 			fputc('T', fpout);
+ 			fwrite(tabentry, sizeof(PgStat_StatTabEntry), 1, fpout);
+ 		}
+ 		
+ 		/*
+ 		 * Mark the end of this DB
+ 		 */
+ 		fputc('d', fpout);
+ 	}
+ 
+ 	/*
+ 	 * Write out the known running backends to the stats file.
+ 	 */
+ 	i = MaxBackends;
+ 	fputc('M', fpout);
+ 	fwrite(&i, sizeof(i), 1, fpout);
+ 
+ 	for (i = 0; i < MaxBackends; i++)
+ 	{
+ 		if (pgStatBeTable[i].databaseid != InvalidOid)
+ 		{
+ 			fputc('B', fpout);
+ 			fwrite(&pgStatBeTable[i], sizeof(PgStat_StatBeEntry), 1, fpout);
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * No more output to be done. Close the temp file and
+ 	 * replace the old pgstat.stat with it's content.
+ 	 */
+ 	fputc('E', fpout);
+ 	if (fclose(fpout) < 0)
+ 	{
+ 		fprintf(stderr, "PGSTAT: Error closing temp stats file\nPGSTAT: ");
+ 		perror(pgStat_tmpfname);
+ 		fprintf(stderr, "PGSTAT: Abort\n");
+ 		fflush(stderr);
+ 		exit(1);
+ 	}
+ 	else
+ 	{
+ 		if (rename(pgStat_tmpfname, pgStat_fname) < 0)
+ 		{
+ 			fprintf(stderr, "PGSTAT: Cannot rename temp stats file\n"
+ 							"PGSTAT: ");
+ 			perror(pgStat_fname);
+ 			fflush(stderr);
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * Clear out the dead backends table
+ 	 */
+ 	hash_seq_init(&hstat, pgStatBeDead);
+ 	while ((hentry = hash_seq_search(&hstat)) != (long *)TRUE)
+ 	{
+ 		if (hentry == NULL)
+ 		{
+ 			fprintf(stderr, "PGSTAT: dead backend hash table corrupted "
+ 							"during cleanup - abort\n");
+ 			exit(1);
+ 		}
+ 		deadbe = (PgStat_StatBeDead *)hentry;
+ 
+ 		/*
+ 		 * Count down the destroy delay and remove entries where
+ 		 * it reaches 0.
+ 		 */
+ 		if (--(deadbe->destroy) <= 0)
+ 		{
+ 			hentry = hash_search(pgStatBeDead, (char *)&(deadbe->procpid),
+ 							HASH_REMOVE, &found);
+ 			if (hentry == NULL)
+ 			{
+ 				fprintf(stderr, "PGSTAT: dead backend hash table corrupted "
+ 								"during cleanup - abort\n");
+ 				exit(1);
+ 			}
+ 		}
+ 	}
+ }
+ 
+ 
+ /* ----------
+  * pgstat_read_statsfile() -
+  *
+  *	Reads in an existing statistics collector and initializes the
+  *	databases hash table (who's entries point to the tables hash tables)
+  *	and the current backend table.
+  * ----------
+  */
+ static void
+ pgstat_read_statsfile(HTAB **dbhash, Oid onlydb, 
+ 			PgStat_StatBeEntry **betab, int *numbackends)
+ {
+ 	PgStat_StatDBEntry		   *dbentry;
+ 	PgStat_StatDBEntry			dbbuf;
+ 	PgStat_StatTabEntry		   *tabentry;
+ 	PgStat_StatTabEntry			tabbuf;
+ 	HASHCTL						hash_ctl;
+ 	HTAB					   *tabhash = NULL;
+ 	FILE					   *fpin;
+ 	int							maxbackends = 0;
+ 	int							havebackends = 0;
+ 	bool						found;
+ 	MemoryContext				use_mcxt;
+ 	int							mcxt_flags;
+ 
+ 	/*
+ 	 * If running in the collector we use the DynaHashCxt
+ 	 * memory context. If running in a backend, we use the
+ 	 * TopTransactionContext instead, so the caller must only know the
+ 	 * last XactId when this call happened to know if his tables are
+ 	 * still valid or already gone!
+ 	 */
+ 	if (pgStatRunningInCollector)
+ 	{
+ 		use_mcxt = NULL;
+ 		mcxt_flags = 0;
+ 	}
+ 	else
+ 	{
+ 		use_mcxt = TopTransactionContext;
+ 		mcxt_flags = HASH_CONTEXT;
+ 	}
+ 
+ 	/*
+ 	 * Create the DB hashtable
+ 	 */
+ 	memset(&hash_ctl, 0, sizeof(hash_ctl));
+ 	hash_ctl.keysize  = sizeof(Oid);
+ 	hash_ctl.datasize = sizeof(PgStat_StatDBEntry);
+ 	hash_ctl.hash     = tag_hash;
+ 	hash_ctl.hcxt     = use_mcxt;
+ 	*dbhash = hash_create(PGSTAT_DB_HASH_SIZE, &hash_ctl, 
+ 							HASH_ELEM | HASH_FUNCTION | mcxt_flags);
+ 	if (pgStatDBHash == NULL)
+ 	{
+ 		if (pgStatRunningInCollector)
+ 		{
+ 			fprintf(stderr, "PGSTAT: Creation of DB hash table failed\n");
+ 			exit(1);
+ 		}
+ 			elog(ERROR, "PGSTAT: Creation of DB hash table failed");
+ 	}
+ 
+ 	/*
+ 	 * Initialize the number of known backends to zero, just in case we
+ 	 * do a silent error return below.
+ 	 */
+ 	if (numbackends != NULL)
+ 		*numbackends = 0;
+ 	if (betab != NULL)
+ 		*betab = NULL;
+ 
+ 	/*
+ 	 * Try to open the status file. If it doesn't exist, the backends
+ 	 * simply return zero for anything and the collector simply
+ 	 * starts from scratch with empty counters.
+ 	 */
+ 	if ((fpin = fopen(pgStat_fname, "r")) == NULL)
+ 		return;
+ 
+ 	/*
+ 	 * We found an existing collector stats file. Read it and put
+ 	 * all the hashtable entries into place.
+ 	 */
+ 	for (;;)
+ 	{
+ 		switch (fgetc(fpin))
+ 		{
+ 			/*
+ 			 * 'D'	A PgStat_StatDBEntry struct describing a database
+ 			 *		follows. Subsequently, zero to many 'T' entries will
+ 			 *		follow until a 'd' is encountered.
+ 			 */
+ 			case 'D':
+ 				if (fread(&dbbuf, 1, sizeof(dbbuf), fpin) != sizeof(dbbuf))
+ 				{
+ 					if (pgStatRunningInCollector)
+ 					{
+ 						fprintf(stderr, 
+ 								"PGSTAT: corrupted pgstat.stat file\n");
+ 						fclose(fpin);
+ 						return;
+ 					}
+ 					else
+ 					{
+ 						elog(NOTICE, 
+ 								"PGSTAT: corrupted pgstat.stat file");
+ 						fclose(fpin);
+ 						return;
+ 					}
+ 				}
+ 
+ 				/*
+ 				 * Add to the DB hash
+ 				 */
+ 				dbentry = (PgStat_StatDBEntry *)hash_search(*dbhash,
+ 								(char *)&dbbuf.databaseid,
+ 								HASH_ENTER, &found);
+ 				if (dbentry == NULL)
+ 				{
+ 					if (pgStatRunningInCollector)
+ 					{
+ 						fprintf(stderr, "PGSTAT: DB hash table corrupted\n");
+ 						exit(1);
+ 					}
+ 					else
+ 					{
+ 						fclose(fpin);
+ 						elog(ERROR, "PGSTAT: DB hash table corrupted");
+ 					}
+ 				}
+ 				if (found)
+ 				{
+ 					if (pgStatRunningInCollector)
+ 					{
+ 						fprintf(stderr, 
+ 								"PGSTAT: corrupted pgstat.stat file\n");
+ 						fclose(fpin);
+ 						return;
+ 					}
+ 					else
+ 					{
+ 						elog(NOTICE, 
+ 								"PGSTAT: corrupted pgstat.stat file");
+ 						fclose(fpin);
+ 						return;
+ 					}
+ 				}
+ 
+ 				memcpy(dbentry, &dbbuf, sizeof(PgStat_StatDBEntry));
+ 				dbentry->tables			= NULL;
+ 				dbentry->destroy		= 0;
+ 				dbentry->n_backends		= 0;
+ 
+ 				/*
+ 				 * Don't collect tables if not the requested DB
+ 				 */
+ 				if (onlydb != InvalidOid && onlydb != dbbuf.databaseid)
+ 					break;
+ 
+ 
+ 				memset(&hash_ctl, 0, sizeof(hash_ctl));
+ 				hash_ctl.keysize  = sizeof(Oid);
+ 				hash_ctl.datasize = sizeof(PgStat_StatTabEntry);
+ 				hash_ctl.hash     = tag_hash;
+ 				hash_ctl.hcxt     = use_mcxt;
+ 				dbentry->tables = hash_create(PGSTAT_TAB_HASH_SIZE, &hash_ctl,
+ 									HASH_ELEM | HASH_FUNCTION | mcxt_flags);
+ 				if (dbentry->tables == NULL)
+ 				{
+ 					if (pgStatRunningInCollector)
+ 					{
+ 						fprintf(stderr, "PGSTAT: failed to initialize "
+ 										"hash table for new database entry\n");
+ 						exit(1);
+ 					}
+ 					else
+ 					{
+ 						fclose(fpin);
+ 						elog(ERROR, "PGSTAT: failed to initialize "
+ 									"hash table for new database entry\n");
+ 					}
+ 				}
+ 
+ 				/*
+ 				 * Arrange that following 'T's add entries to this
+ 				 * databases tables hash table.
+ 				 */
+ 				tabhash = dbentry->tables;
+ 				break;
+ 
+ 			/*
+ 			 * 'd'	End of this database.
+ 			 */
+ 			case 'd':
+ 				tabhash = NULL;
+ 				break;
+ 
+ 			/*
+ 			 * 'T'	A PgStat_StatTabEntry follows.
+ 			 */
+ 			case 'T':
+ 				if (fread(&tabbuf, 1, sizeof(tabbuf), fpin) != sizeof(tabbuf))
+ 				{
+ 					if (pgStatRunningInCollector)
+ 					{
+ 						fprintf(stderr, 
+ 								"PGSTAT: corrupted pgstat.stat file\n");
+ 						fclose(fpin);
+ 						return;
+ 					}
+ 					else
+ 					{
+ 						elog(NOTICE, 
+ 								"PGSTAT: corrupted pgstat.stat file");
+ 						fclose(fpin);
+ 						return;
+ 					}
+ 				}
+ 
+ 				/*
+ 				 * Skip if table belongs to a not requested database.
+ 				 */
+ 				if (tabhash == NULL)
+ 					break;
+ 
+ 				tabentry = (PgStat_StatTabEntry *)hash_search(tabhash,
+ 								(char *)&tabbuf.tableid,
+ 								HASH_ENTER, &found);
+ 				if (tabentry == NULL)
+ 				{
+ 					if (pgStatRunningInCollector)
+ 					{
+ 						fprintf(stderr, "PGSTAT: Tab hash table corrupted\n");
+ 						exit(1);
+ 					}
+ 					else
+ 					{
+ 						fclose(fpin);
+ 						elog(ERROR, "PGSTAT: Tab hash table corrupted");
+ 					}
+ 				}
+ 
+ 				if (found)
+ 				{
+ 					if (pgStatRunningInCollector)
+ 					{
+ 						fprintf(stderr, 
+ 								"PGSTAT: corrupted pgstat.stat file\n");
+ 						fclose(fpin);
+ 						return;
+ 					}
+ 					else
+ 					{
+ 						elog(NOTICE, 
+ 								"PGSTAT: corrupted pgstat.stat file");
+ 						fclose(fpin);
+ 						return;
+ 					}
+ 				}
+ 
+ 				memcpy(tabentry, &tabbuf, sizeof(tabbuf));
+ 				break;
+ 
+ 			/*
+ 			 * 'M'	The maximum number of backends to expect follows.
+ 			 */
+ 			case 'M':
+ 				if (betab == NULL || numbackends == NULL)
+ 				{
+ 					fclose(fpin);
+ 					return;
+ 				}
+ 				if (fread(&maxbackends, 1, sizeof(maxbackends), fpin) !=
+ 							sizeof(maxbackends))
+ 				{
+ 					if (pgStatRunningInCollector)
+ 					{
+ 						fprintf(stderr, 
+ 								"PGSTAT: corrupted pgstat.stat file\n");
+ 						fclose(fpin);
+ 						return;
+ 					}
+ 					else
+ 					{
+ 						elog(NOTICE, 
+ 								"PGSTAT: corrupted pgstat.stat file");
+ 						fclose(fpin);
+ 						return;
+ 					}
+ 				}
+ 				if (maxbackends == 0)
+ 				{
+ 					fclose(fpin);
+ 					return;
+ 				}
+ 
+ 				/*
+ 				 * Allocate space (in TopTransactionContext too) for
+ 				 * the backend table.
+ 				 */
+ 				if (use_mcxt == NULL)
+ 					*betab = (PgStat_StatBeEntry *)malloc(
+ 								sizeof(PgStat_StatBeEntry) * maxbackends);
+ 				else
+ 					*betab = (PgStat_StatBeEntry *)MemoryContextAlloc(
+ 								use_mcxt, 
+ 								sizeof(PgStat_StatBeEntry) * maxbackends);
+ 				break;
+ 
+ 			/*
+ 			 * 'B'	A PgStat_StatBeEntry follows.
+ 			 */
+ 			case 'B':
+ 				if (betab == NULL || numbackends == NULL)
+ 				{
+ 					fclose(fpin);
+ 					return;
+ 				}
+ 				if (*betab == NULL)
+ 				{
+ 					fclose(fpin);
+ 					return;
+ 				}
+ 				/*
+ 				 * Read it directly into the table.
+ 				 */
+ 				if (fread(&(*betab)[havebackends], 1, 
+ 							sizeof(PgStat_StatBeEntry), fpin) !=
+ 							sizeof(PgStat_StatBeEntry))
+ 				{
+ 					if (pgStatRunningInCollector)
+ 					{
+ 						fprintf(stderr, 
+ 								"PGSTAT: corrupted pgstat.stat file\n");
+ 						fclose(fpin);
+ 						return;
+ 					}
+ 					else
+ 					{
+ 						elog(NOTICE, 
+ 								"PGSTAT: corrupted pgstat.stat file");
+ 						fclose(fpin);
+ 						return;
+ 					}
+ 				}
+ 
+ 				/*
+ 				 * Count backends per database here.
+ 				 */
+ 				dbentry = (PgStat_StatDBEntry *)hash_search(*dbhash,
+ 								(char *)&((*betab)[havebackends].databaseid),
+ 								HASH_FIND, &found);
+ 				if (found)
+ 					dbentry->n_backends++;
+ 
+ 				havebackends++;
+ 				if (numbackends != 0)
+ 					*numbackends = havebackends;
+ 				if (havebackends >= maxbackends)
+ 				{
+ 					fclose(fpin);
+ 					return;
+ 				}
+ 				break;
+ 
+ 			/*
+ 			 * 'E'	The EOF marker of a complete stats file.
+ 			 */
+ 			case 'E':
+ 				fclose(fpin);
+ 				return;
+ 
+ 			default:
+ 				if (pgStatRunningInCollector)
+ 				{
+ 					fprintf(stderr, "PGSTAT: corrupted pgstat.stat file\n");
+ 					fclose(fpin);
+ 					return;
+ 				}
+ 				else
+ 				{
+ 					elog(NOTICE, "PGSTAT: corrupted pgstat.stat file");
+ 					fclose(fpin);
+ 					return;
+ 				}
+ 		}
+ 	}
+ 
+ 	fclose(fpin);
+ }
+ 
+ 
+ /* ----------
+  * pgstat_recv_bestart() -
+  *
+  *	Process a backend starup message.
+  * ----------
+  */
+ static void
+ pgstat_recv_bestart(PgStat_MsgBestart *msg, int len)
+ {
+ 	pgstat_add_backend(&msg->m_hdr);
+ }
+ 
+ 
+ /* ----------
+  * pgstat_recv_beterm() -
+  *
+  *	Process a backend termination message.
+  * ----------
+  */
+ static void
+ pgstat_recv_beterm(PgStat_MsgBeterm *msg, int len)
+ {
+ 	pgstat_sub_backend(msg->m_hdr.m_procpid);
+ }
+ 
+ 
+ /* ----------
+  * pgstat_recv_activity() -
+  *
+  *	Remember what the backend is doing.
+  * ----------
+  */
+ static void
+ pgstat_recv_activity(PgStat_MsgActivity *msg, int len)
+ {
+ 	/*
+ 	 * Here we check explicitly for 0 return, since we don't
+ 	 * want to mangle the activity of an active backend by
+ 	 * a delayed packed from a dead one.
+ 	 */
+ 	if (pgstat_add_backend(&msg->m_hdr) != 0)
+ 		return;
+ 		
+ 	strncpy(pgStatBeTable[msg->m_hdr.m_backendid - 1].activity,
+ 			msg->m_what, PGSTAT_ACTIVITY_SIZE);
+ }
+ 
+ 
+ /* ----------
+  * pgstat_recv_tabstat() -
+  *
+  *	Count what the backend has done.
+  * ----------
+  */
+ static void
+ pgstat_recv_tabstat(PgStat_MsgTabstat *msg, int len)
+ {
+ 	PgStat_TableEntry	   *tabmsg = &(msg->m_entry[0]);
+ 	PgStat_StatDBEntry     *dbentry;
+ 	PgStat_StatTabEntry    *tabentry;
+ 	int						i;
+ 	bool					found;
+ 
+ 	/*
+ 	 * Make sure the backend is counted for.
+ 	 */
+ 	if (pgstat_add_backend(&msg->m_hdr) < 0)
+ 		return;
+ 
+ 	/*
+ 	 * Lookup the database in the hashtable.
+ 	 */
+ 	dbentry = (PgStat_StatDBEntry *)hash_search(pgStatDBHash,
+ 							(char *)&(msg->m_hdr.m_databaseid),
+ 							HASH_FIND, &found);
+ 	if (dbentry == NULL)
+ 	{
+ 		fprintf(stderr, "PGSTAT: database hash table corrupted - abort\n");
+ 		exit(1);
+ 	}
+ 	if (!found)
+ 		return;
+ 
+ 	/*
+ 	 * If the database is marked for destroy, this is a delayed
+ 	 * UDP packet and not worth beeing counted.
+ 	 */
+ 	if (dbentry->destroy > 0)
+ 		return;
+ 
+ 	dbentry->n_xact_commit   += (PgStat_Counter)(msg->m_xact_commit);
+ 	dbentry->n_xact_rollback += (PgStat_Counter)(msg->m_xact_rollback);
+ 
+ 	/*
+ 	 * Process all table entries in the message.
+ 	 */
+ 	for (i = 0; i < msg->m_nentries; i++)
+ 	{
+ 		tabentry = (PgStat_StatTabEntry *)hash_search(dbentry->tables,
+ 						(char *)&(tabmsg[i].t_id), 
+ 						HASH_ENTER, &found);
+ 		if (tabentry == NULL)
+ 		{
+ 			fprintf(stderr, "PGSTAT: tables hash table corrupted for "
+ 							"database %d - abort\n", dbentry->databaseid);
+ 			exit(1);
+ 		}
+ 
+ 		if (!found)
+ 		{
+ 			/*
+ 			 * If it's a new table entry, initialize counters
+ 			 * to the values we just got.
+ 			 */
+ 			tabentry->numscans			= tabmsg[i].t_numscans;
+ 			tabentry->tuples_returned	= tabmsg[i].t_tuples_returned;
+ 			tabentry->tuples_fetched	= tabmsg[i].t_tuples_fetched;
+ 			tabentry->tuples_inserted	= tabmsg[i].t_tuples_inserted;
+ 			tabentry->tuples_updated	= tabmsg[i].t_tuples_updated;
+ 			tabentry->tuples_deleted	= tabmsg[i].t_tuples_deleted;
+ 			tabentry->blocks_fetched	= tabmsg[i].t_blocks_fetched;
+ 			tabentry->blocks_hit		= tabmsg[i].t_blocks_hit;
+ 
+ 			tabentry->destroy = 0;
+ 		}
+ 		else
+ 		{
+ 			/*
+ 			 * Otherwise add the values to the existing entry.
+ 			 */
+ 			tabentry->numscans			+= tabmsg[i].t_numscans;
+ 			tabentry->tuples_returned	+= tabmsg[i].t_tuples_returned;
+ 			tabentry->tuples_fetched	+= tabmsg[i].t_tuples_fetched;
+ 			tabentry->tuples_inserted	+= tabmsg[i].t_tuples_inserted;
+ 			tabentry->tuples_updated	+= tabmsg[i].t_tuples_updated;
+ 			tabentry->tuples_deleted	+= tabmsg[i].t_tuples_deleted;
+ 			tabentry->blocks_fetched	+= tabmsg[i].t_blocks_fetched;
+ 			tabentry->blocks_hit		+= tabmsg[i].t_blocks_hit;
+ 		}
+ 
+ 		/*
+ 		 * And add the block IO to the database entry.
+ 		 */
+ 		dbentry->n_blocks_fetched	+= tabmsg[i].t_blocks_fetched;
+ 		dbentry->n_blocks_hit		+= tabmsg[i].t_blocks_hit;
+ 	}
+ }
+ 
+ 
+ /* ----------
+  * pgstat_recv_tabpurge() -
+  *
+  *	Arrange for dead table removal.
+  * ----------
+  */
+ static void
+ pgstat_recv_tabpurge(PgStat_MsgTabpurge *msg, int len)
+ {
+ 	PgStat_StatDBEntry     *dbentry;
+ 	PgStat_StatTabEntry    *tabentry;
+ 	int						i;
+ 	bool					found;
+ 
+ 	/*
+ 	 * Make sure the backend is counted for.
+ 	 */
+ 	if (pgstat_add_backend(&msg->m_hdr) < 0)
+ 		return;
+ 
+ 	/*
+ 	 * Lookup the database in the hashtable.
+ 	 */
+ 	dbentry = (PgStat_StatDBEntry *)hash_search(pgStatDBHash,
+ 							(char *)&(msg->m_hdr.m_databaseid),
+ 							HASH_FIND, &found);
+ 	if (dbentry == NULL)
+ 	{
+ 		fprintf(stderr, "PGSTAT: database hash table corrupted - abort\n");
+ 		exit(1);
+ 	}
+ 	if (!found)
+ 		return;
+ 
+ 	/*
+ 	 * If the database is marked for destroy, this is a delayed
+ 	 * UDP packet and the tables will go away at DB destruction.
+ 	 */
+ 	if (dbentry->destroy > 0)
+ 		return;
+ 
+ 	/*
+ 	 * Process all table entries in the message.
+ 	 */
+ 	for (i = 0; i < msg->m_nentries; i++)
+ 	{
+ 		tabentry = (PgStat_StatTabEntry *)hash_search(dbentry->tables,
+ 						(char *)&(msg->m_tableid[i]), 
+ 						HASH_FIND, &found);
+ 		if (tabentry == NULL)
+ 		{
+ 			fprintf(stderr, "PGSTAT: tables hash table corrupted for "
+ 							"database %d - abort\n", dbentry->databaseid);
+ 			exit(1);
+ 		}
+ 
+ 		if (found)
+ 			tabentry->destroy = PGSTAT_DESTROY_COUNT;
+ 	}
+ }
+ 
+ 
+ /* ----------
+  * pgstat_recv_dropdb() -
+  *
+  *	Arrange for dead database removal
+  * ----------
+  */
+ static void
+ pgstat_recv_dropdb(PgStat_MsgDropdb *msg, int len)
+ {
+ 	PgStat_StatDBEntry     *dbentry;
+ 	bool					found;
+ 
+ 	/*
+ 	 * Make sure the backend is counted for.
+ 	 */
+ 	if (pgstat_add_backend(&msg->m_hdr) < 0)
+ 		return;
+ 
+ 	/*
+ 	 * Lookup the database in the hashtable.
+ 	 */
+ 	dbentry = (PgStat_StatDBEntry *)hash_search(pgStatDBHash,
+ 							(char *)&(msg->m_databaseid),
+ 							HASH_FIND, &found);
+ 	if (dbentry == NULL)
+ 	{
+ 		fprintf(stderr, "PGSTAT: database hash table corrupted - abort\n");
+ 		exit(1);
+ 	}
+ 	if (!found)
+ 		return;
+ 
+ 	/*
+ 	 * Mark the database for destruction.
+ 	 */
+ 	dbentry->destroy = PGSTAT_DESTROY_COUNT;
+ }
+ 
+ 
+ /* ----------
+  * pgstat_recv_dropdb() -
+  *
+  *	Arrange for dead database removal
+  * ----------
+  */
+ static void
+ pgstat_recv_resetcounter(PgStat_MsgResetcounter *msg, int len)
+ {
+ 	HASHCTL					hash_ctl;
+ 	PgStat_StatDBEntry     *dbentry;
+ 	bool					found;
+ 
+ 	/*
+ 	 * Make sure the backend is counted for.
+ 	 */
+ 	if (pgstat_add_backend(&msg->m_hdr) < 0)
+ 		return;
+ 
+ 	/*
+ 	 * Lookup the database in the hashtable.
+ 	 */
+ 	dbentry = (PgStat_StatDBEntry *)hash_search(pgStatDBHash,
+ 							(char *)&(msg->m_hdr.m_databaseid),
+ 							HASH_FIND, &found);
+ 	if (dbentry == NULL)
+ 	{
+ 		fprintf(stderr, "PGSTAT: database hash table corrupted - abort\n");
+ 		exit(1);
+ 	}
+ 	if (!found)
+ 		return;
+ 
+ 	/*
+ 	 * We simply throw away all the databases table entries by recreating
+ 	 * a new hash table for them.
+ 	 */
+ 	if (dbentry->tables != NULL)
+ 		hash_destroy(dbentry->tables);
+ 
+ 	dbentry->tables				= NULL;
+ 	dbentry->n_xact_commit		= 0;
+ 	dbentry->n_xact_rollback	= 0;
+ 	dbentry->n_blocks_fetched	= 0;
+ 	dbentry->n_blocks_hit		= 0;
+ 	dbentry->n_connects			= 0;
+ 	dbentry->destroy			= 0;
+ 
+ 	memset(&hash_ctl, 0, sizeof(hash_ctl));
+ 	hash_ctl.keysize  = sizeof(Oid);
+ 	hash_ctl.datasize = sizeof(PgStat_StatTabEntry);
+ 	hash_ctl.hash     = tag_hash;
+ 	dbentry->tables = hash_create(PGSTAT_TAB_HASH_SIZE, &hash_ctl,
+ 						HASH_ELEM | HASH_FUNCTION);
+ 	if (dbentry->tables == NULL)
+ 	{
+ 		fprintf(stderr, "PGSTAT: failed to reinitialize hash table for "
+ 						"database entry\n");
+ 		exit(1);
+ 	}
+ }
+ 
+ 
diff -crN ../src.orig/backend/postmaster/postmaster.c ./backend/postmaster/postmaster.c
*** ../src.orig/backend/postmaster/postmaster.c	Sat Jun  2 11:17:49 2001
--- ./backend/postmaster/postmaster.c	Sat Jun  2 11:18:33 2001
***************
*** 96,101 ****
--- 96,103 ----
  #include "utils/memutils.h"
  #include "bootstrap/bootstrap.h"
  
+ #include "pgstat.h"
+ 
  #define INVALID_SOCK	(-1)
  #define ARGV_SIZE	64
  
***************
*** 560,565 ****
--- 562,575 ----
  		ExitPostmaster(1);
  	}
  
+ 	/*
+ 	 * Initialize and startup the statistics collector process
+ 	 */
+ 	if (pgstat_init() < 0)
+ 		ExitPostmaster(1);
+ 	if (pgstat_start() < 0)
+ 		ExitPostmaster(1);
+ 
  	if (DebugLvl > 2)
  	{
  		extern char **environ;
***************
*** 1586,1591 ****
--- 1596,1613 ----
  	{
  		exitstatus = status.w_status;
  #endif
+ 		/*
+ 		 * Check if this child was the statistics collector. If
+ 		 * so, start a new one. 
+ 		 */
+ 		if (pgstat_ispgstat(pid))
+ 		{
+ 			fprintf(stderr, "%s: Performance collector exited with status %d\n",
+ 					progname, exitstatus);
+ 			pgstat_start();
+ 			continue;
+ 		}
+ 
  		if (ShutdownPID > 0)
  		{
  			if (pid != ShutdownPID)
***************
*** 1633,1638 ****
--- 1655,1661 ----
  			errno = save_errno;
  			return;
  		}
+ 
  		CleanupProc(pid, exitstatus);
  	}
  
***************
*** 1736,1741 ****
--- 1759,1769 ----
  			/* Why is this done here, and not by the backend itself? */
  			if (!FatalError)
  				ProcRemove(pid);
+ 
+ 			/*
+ 			 * Tell the collector about backend termination
+ 			 */
+ 			pgstat_beterm(pid);
  		}
  
  		return;
***************
*** 1799,1804 ****
--- 1827,1839 ----
  	{
  		CheckPointPID = 0;
  		checkpointed = 0;
+ 	}
+ 	else
+ 	{
+ 		/*
+ 		 * Tell the collector about backend termination
+ 		 */
+ 		pgstat_beterm(pid);
  	}
  
  	FatalError = true;
diff -crN ../src.orig/backend/storage/buffer/bufmgr.c ./backend/storage/buffer/bufmgr.c
*** ../src.orig/backend/storage/buffer/bufmgr.c	Sat Jun  2 11:17:49 2001
--- ./backend/storage/buffer/bufmgr.c	Sat Jun  2 11:18:33 2001
***************
*** 55,60 ****
--- 55,62 ----
  #include "utils/relcache.h"
  #include "catalog/pg_database.h"
  
+ #include "pgstat.h"
+ 
  #define BufferGetLSN(bufHdr)	\
  	(*((XLogRecPtr*)MAKE_PTR((bufHdr)->data)))
  
***************
*** 183,188 ****
--- 185,191 ----
  	if (isLocalBuf)
  	{
  		ReadLocalBufferCount++;
+ 		pgstat_count_buffer_read(&reln->pgstat_info, reln);
  		/* Substitute proper block number if caller asked for P_NEW */
  		if (blockNum == P_NEW)
  		{
***************
*** 192,202 ****
--- 195,209 ----
  		}
  		bufHdr = LocalBufferAlloc(reln, blockNum, &found);
  		if (found)
+ 		{
  			LocalBufferHitCount++;
+ 			pgstat_count_buffer_hit(&reln->pgstat_info, reln);
+ 		}
  	}
  	else
  	{
  		ReadBufferCount++;
+ 		pgstat_count_buffer_read(&reln->pgstat_info, reln);
  		/* Substitute proper block number if caller asked for P_NEW */
  		if (blockNum == P_NEW)
  		{
***************
*** 211,217 ****
--- 218,227 ----
  			SpinAcquire(BufMgrLock);
  		bufHdr = BufferAlloc(reln, blockNum, &found);
  		if (found)
+ 		{
  			BufferHitCount++;
+ 			pgstat_count_buffer_hit(&reln->pgstat_info, reln);
+ 		}
  	}
  
  	/* At this point we do NOT hold the bufmgr spinlock. */
diff -crN ../src.orig/backend/storage/large_object/inv_api.c ./backend/storage/large_object/inv_api.c
*** ../src.orig/backend/storage/large_object/inv_api.c	Sat Jun  2 11:17:49 2001
--- ./backend/storage/large_object/inv_api.c	Sat Jun  2 11:18:33 2001
***************
*** 227,233 ****
  	while ((indexRes = index_getnext(sd, BackwardScanDirection)))
  	{
  		tuple.t_self = indexRes->heap_iptr;
! 		heap_fetch(obj_desc->heap_r, SnapshotNow, &tuple, &buffer);
  		pfree(indexRes);
  		if (tuple.t_data == NULL)
  			continue;
--- 227,233 ----
  	while ((indexRes = index_getnext(sd, BackwardScanDirection)))
  	{
  		tuple.t_self = indexRes->heap_iptr;
! 		heap_fetch(obj_desc->heap_r, SnapshotNow, &tuple, &buffer, sd);
  		pfree(indexRes);
  		if (tuple.t_data == NULL)
  			continue;
***************
*** 339,345 ****
  	while ((indexRes = index_getnext(sd, ForwardScanDirection)))
  	{
  		tuple.t_self = indexRes->heap_iptr;
! 		heap_fetch(obj_desc->heap_r, SnapshotNow, &tuple, &buffer);
  		pfree(indexRes);
  
  		if (tuple.t_data == NULL)
--- 339,345 ----
  	while ((indexRes = index_getnext(sd, ForwardScanDirection)))
  	{
  		tuple.t_self = indexRes->heap_iptr;
! 		heap_fetch(obj_desc->heap_r, SnapshotNow, &tuple, &buffer, sd);
  		pfree(indexRes);
  
  		if (tuple.t_data == NULL)
***************
*** 473,479 ****
  			while ((indexRes = index_getnext(sd, ForwardScanDirection)))
  			{
  				oldtuple.t_self = indexRes->heap_iptr;
! 				heap_fetch(obj_desc->heap_r, SnapshotNow, &oldtuple, &buffer);
  				pfree(indexRes);
  				if (oldtuple.t_data != NULL)
  				{
--- 473,479 ----
  			while ((indexRes = index_getnext(sd, ForwardScanDirection)))
  			{
  				oldtuple.t_self = indexRes->heap_iptr;
! 				heap_fetch(obj_desc->heap_r, SnapshotNow, &oldtuple, &buffer, sd);
  				pfree(indexRes);
  				if (oldtuple.t_data != NULL)
  				{
diff -crN ../src.orig/backend/tcop/postgres.c ./backend/tcop/postgres.c
*** ../src.orig/backend/tcop/postgres.c	Sat Jun  2 11:17:49 2001
--- ./backend/tcop/postgres.c	Sat Jun  2 11:18:33 2001
***************
*** 65,70 ****
--- 65,71 ----
  #include "mb/pg_wchar.h"
  #endif
  
+ #include "pgstat.h"
  
  /* ----------------
   *		global variables
***************
*** 1726,1731 ****
--- 1727,1739 ----
  										 ALLOCSET_DEFAULT_INITSIZE,
  										 ALLOCSET_DEFAULT_MAXSIZE);
  
+ 	/* ----------
+ 	 * Tell the statistics collector that we're alive and
+ 	 * to which database we belong.
+ 	 * ----------
+ 	 */
+ 	pgstat_bestart();
+ 
  	/*
  	 * POSTGRES main processing loop begins here
  	 *
***************
*** 1811,1820 ****
--- 1819,1841 ----
  		 */
  		ReadyForQuery(whereToSendOutput);
  
+ 		/* ----------
+ 		 * Tell the statistics collector what we've collected
+ 		 * so far.
+ 		 * ----------
+ 		 */
+ 		pgstat_report_tabstat();
+ 
  		if (IsTransactionBlock())
+ 		{
  			set_ps_display("idle in transaction");
+ 			pgstat_report_activity("<IDLE> in transaction");
+ 		}
  		else
+ 		{
  			set_ps_display("idle");
+ 			pgstat_report_activity("<IDLE>");
+ 		}
  
  		/*
  		 * (2) deal with pending asynchronous NOTIFY from other backends,
***************
*** 1865,1870 ****
--- 1886,1897 ----
  				 * 'F' indicates a fastpath call.
  				 */
  			case 'F':
+ 				/* ----------
+ 				 * Tell the collector what we're doing
+ 				 * ----------
+ 				 */
+ 				pgstat_report_activity("<FASTPATH> function call");
+ 
  				/* start an xact for this function invocation */
  				start_xact_command();
  
***************
*** 1904,1909 ****
--- 1931,1938 ----
  					 */
  					if (Show_query_stats)
  						ResetUsage();
+ 
+ 					pgstat_report_activity(parser_input->data);
  
  					pg_exec_query_string(parser_input->data,
  										 whereToSendOutput,
diff -crN ../src.orig/backend/utils/adt/Makefile ./backend/utils/adt/Makefile
*** ../src.orig/backend/utils/adt/Makefile	Sat Jun  2 11:17:49 2001
--- ./backend/utils/adt/Makefile	Sat Jun  2 11:18:33 2001
***************
*** 24,30 ****
  	tid.o timestamp.o varbit.o varchar.o varlena.o version.o \
  	network.o mac.o inet_net_ntop.o inet_net_pton.o \
  	ri_triggers.o pg_lzcompress.o pg_locale.o formatting.o \
! 	ascii.o quote.o
  
  all: SUBSYS.o
  
--- 24,30 ----
  	tid.o timestamp.o varbit.o varchar.o varlena.o version.o \
  	network.o mac.o inet_net_ntop.o inet_net_pton.o \
  	ri_triggers.o pg_lzcompress.o pg_locale.o formatting.o \
! 	ascii.o quote.o pgstatfuncs.o
  
  all: SUBSYS.o
  
diff -crN ../src.orig/backend/utils/adt/pgstatfuncs.c ./backend/utils/adt/pgstatfuncs.c
*** ../src.orig/backend/utils/adt/pgstatfuncs.c	Wed Dec 31 19:00:00 1969
--- ./backend/utils/adt/pgstatfuncs.c	Sat Jun  2 11:18:33 2001
***************
*** 0 ****
--- 1,399 ----
+ #include "postgres.h"
+ #include "fmgr.h"
+ 
+ #include "utils/hsearch.h"
+ #include "access/xact.h"
+ #include "catalog/pg_shadow.h"
+ #include "nodes/execnodes.h"
+ 
+ #include "pgstat.h"
+ 
+ extern Datum	pg_stat_get_numscans(PG_FUNCTION_ARGS);
+ extern Datum	pg_stat_get_tuples_returned(PG_FUNCTION_ARGS);
+ extern Datum	pg_stat_get_tuples_fetched(PG_FUNCTION_ARGS);
+ extern Datum	pg_stat_get_tuples_inserted(PG_FUNCTION_ARGS);
+ extern Datum	pg_stat_get_tuples_updated(PG_FUNCTION_ARGS);
+ extern Datum	pg_stat_get_tuples_deleted(PG_FUNCTION_ARGS);
+ extern Datum	pg_stat_get_blocks_fetched(PG_FUNCTION_ARGS);
+ extern Datum	pg_stat_get_blocks_hit(PG_FUNCTION_ARGS);
+ 
+ extern Datum	pg_stat_get_backend_idset(PG_FUNCTION_ARGS);
+ extern Datum	pg_stat_get_backend_pid(PG_FUNCTION_ARGS);
+ extern Datum	pg_stat_get_backend_dbid(PG_FUNCTION_ARGS);
+ extern Datum	pg_stat_get_backend_userid(PG_FUNCTION_ARGS);
+ extern Datum	pg_stat_get_backend_activity(PG_FUNCTION_ARGS);
+ 
+ extern Datum	pg_stat_get_db_numbackends(PG_FUNCTION_ARGS);
+ extern Datum	pg_stat_get_db_xact_commit(PG_FUNCTION_ARGS);
+ extern Datum	pg_stat_get_db_xact_rollback(PG_FUNCTION_ARGS);
+ extern Datum	pg_stat_get_db_blocks_fetched(PG_FUNCTION_ARGS);
+ extern Datum	pg_stat_get_db_blocks_hit(PG_FUNCTION_ARGS);
+ 
+ 
+ 
+ PG_FUNCTION_INFO_V1(pg_stat_get_numscans);
+ PG_FUNCTION_INFO_V1(pg_stat_get_tuples_returned);
+ PG_FUNCTION_INFO_V1(pg_stat_get_tuples_fetched);
+ PG_FUNCTION_INFO_V1(pg_stat_get_tuples_inserted);
+ PG_FUNCTION_INFO_V1(pg_stat_get_tuples_updated);
+ PG_FUNCTION_INFO_V1(pg_stat_get_tuples_deleted);
+ PG_FUNCTION_INFO_V1(pg_stat_get_blocks_fetched);
+ PG_FUNCTION_INFO_V1(pg_stat_get_blocks_hit);
+ 
+ PG_FUNCTION_INFO_V1(pg_stat_get_backend_idset);
+ PG_FUNCTION_INFO_V1(pg_stat_get_backend_pid);
+ PG_FUNCTION_INFO_V1(pg_stat_get_backend_dbid);
+ PG_FUNCTION_INFO_V1(pg_stat_get_backend_userid);
+ PG_FUNCTION_INFO_V1(pg_stat_get_backend_activity);
+ 
+ PG_FUNCTION_INFO_V1(pg_stat_get_db_numbackends);
+ PG_FUNCTION_INFO_V1(pg_stat_get_db_xact_commit);
+ PG_FUNCTION_INFO_V1(pg_stat_get_db_xact_rollback);
+ PG_FUNCTION_INFO_V1(pg_stat_get_db_blocks_fetched);
+ PG_FUNCTION_INFO_V1(pg_stat_get_db_blocks_hit);
+ 
+ 
+ Datum
+ pg_stat_get_numscans(PG_FUNCTION_ARGS)
+ {
+ 	PgStat_StatTabEntry	   *tabentry;
+ 	Oid						relid;
+ 	int64					result;
+ 
+ 	relid  = PG_GETARG_OID(0);
+ 
+ 	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)
+ 		result = 0;
+ 	else
+ 		result = (int64)(tabentry->numscans);
+ 
+ 	PG_RETURN_INT64(result);
+ }
+ 
+ 
+ Datum
+ pg_stat_get_tuples_returned(PG_FUNCTION_ARGS)
+ {
+ 	PgStat_StatTabEntry	   *tabentry;
+ 	Oid						relid;
+ 	int64					result;
+ 
+ 	relid  = PG_GETARG_OID(0);
+ 
+ 	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)
+ 		result = 0;
+ 	else
+ 		result = (int64)(tabentry->tuples_returned);
+ 
+ 	PG_RETURN_INT64(result);
+ }
+ 
+ 
+ Datum
+ pg_stat_get_tuples_fetched(PG_FUNCTION_ARGS)
+ {
+ 	PgStat_StatTabEntry	   *tabentry;
+ 	Oid						relid;
+ 	int64					result;
+ 
+ 	relid  = PG_GETARG_OID(0);
+ 
+ 	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)
+ 		result = 0;
+ 	else
+ 		result = (int64)(tabentry->tuples_fetched);
+ 
+ 	PG_RETURN_INT64(result);
+ }
+ 
+ 
+ Datum
+ pg_stat_get_tuples_inserted(PG_FUNCTION_ARGS)
+ {
+ 	PgStat_StatTabEntry	   *tabentry;
+ 	Oid						relid;
+ 	int64					result;
+ 
+ 	relid  = PG_GETARG_OID(0);
+ 
+ 	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)
+ 		result = 0;
+ 	else
+ 		result = (int64)(tabentry->tuples_inserted);
+ 
+ 	PG_RETURN_INT64(result);
+ }
+ 
+ 
+ Datum
+ pg_stat_get_tuples_updated(PG_FUNCTION_ARGS)
+ {
+ 	PgStat_StatTabEntry	   *tabentry;
+ 	Oid						relid;
+ 	int64					result;
+ 
+ 	relid  = PG_GETARG_OID(0);
+ 
+ 	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)
+ 		result = 0;
+ 	else
+ 		result = (int64)(tabentry->tuples_updated);
+ 
+ 	PG_RETURN_INT64(result);
+ }
+ 
+ 
+ Datum
+ pg_stat_get_tuples_deleted(PG_FUNCTION_ARGS)
+ {
+ 	PgStat_StatTabEntry	   *tabentry;
+ 	Oid						relid;
+ 	int64					result;
+ 
+ 	relid  = PG_GETARG_OID(0);
+ 
+ 	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)
+ 		result = 0;
+ 	else
+ 		result = (int64)(tabentry->tuples_deleted);
+ 
+ 	PG_RETURN_INT64(result);
+ }
+ 
+ 
+ Datum
+ pg_stat_get_blocks_fetched(PG_FUNCTION_ARGS)
+ {
+ 	PgStat_StatTabEntry	   *tabentry;
+ 	Oid						relid;
+ 	int64					result;
+ 
+ 	relid  = PG_GETARG_OID(0);
+ 
+ 	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)
+ 		result = 0;
+ 	else
+ 		result = (int64)(tabentry->blocks_fetched);
+ 
+ 	PG_RETURN_INT64(result);
+ }
+ 
+ 
+ Datum
+ pg_stat_get_blocks_hit(PG_FUNCTION_ARGS)
+ {
+ 	PgStat_StatTabEntry	   *tabentry;
+ 	Oid						relid;
+ 	int64					result;
+ 
+ 	relid  = PG_GETARG_OID(0);
+ 
+ 	if ((tabentry = pgstat_fetch_stat_tabentry(relid)) == NULL)
+ 		result = 0;
+ 	else
+ 		result = (int64)(tabentry->blocks_hit);
+ 
+ 	PG_RETURN_INT64(result);
+ }
+ 
+ 
+ Datum
+ pg_stat_get_backend_idset(PG_FUNCTION_ARGS)
+ {
+ 	FmgrInfo	   *fmgr_info = fcinfo->flinfo;
+ 	MemoryContext	oldcxt;
+ 	int32			result;
+ 
+ 	if (fcinfo->resultinfo == NULL)
+ 		elog(ERROR, "called in context that does not accept a set result (1)");
+ 	if (!IsA(fcinfo->resultinfo, ReturnSetInfo))
+ 		elog(ERROR, "called in context that does not accept a set result (2)");
+ 
+ 	if (fmgr_info->fn_extra == NULL)
+ 	{
+ 		if (fmgr_info->fn_mcxt == NULL)
+ 			elog(ERROR, "No function memory context in set-function");
+ 
+ 		oldcxt = MemoryContextSwitchTo(fmgr_info->fn_mcxt);
+ 		fmgr_info->fn_extra = palloc(sizeof(int32) * 2);
+ 		((int32 *)(fmgr_info->fn_extra))[0] = 0;
+ 		((int32 *)(fmgr_info->fn_extra))[1] = 
+ 						(int32)pgstat_fetch_stat_numbackends();
+ 		MemoryContextSwitchTo(oldcxt);
+ 	}
+ 
+ 	((int32 *)(fmgr_info->fn_extra))[0] += 1;
+ 	result = ((int32 *)(fmgr_info->fn_extra))[0];
+ 
+ 
+ 	if (result > ((int32 *)(fmgr_info->fn_extra))[1])
+ 	{
+ 		((ReturnSetInfo *)(fcinfo->resultinfo))->isDone = ExprEndResult;
+ 		PG_RETURN_NULL();
+ 	}
+ 
+ 	((ReturnSetInfo *)(fcinfo->resultinfo))->isDone = ExprMultipleResult;
+ 	PG_RETURN_INT32(result);
+ }
+ 
+ 
+ Datum
+ pg_stat_get_backend_pid(PG_FUNCTION_ARGS)
+ {
+ 	PgStat_StatBeEntry	   *beentry;
+ 	int32					beid;
+ 
+ 	beid  = PG_GETARG_INT32(0);
+ 
+ 	if ((beentry = pgstat_fetch_stat_beentry(beid)) == NULL)
+ 		PG_RETURN_NULL();
+ 
+ 	PG_RETURN_INT32(beentry->procpid);
+ }
+ 
+ 
+ Datum
+ pg_stat_get_backend_dbid(PG_FUNCTION_ARGS)
+ {
+ 	PgStat_StatBeEntry	   *beentry;
+ 	int32					beid;
+ 
+ 	beid  = PG_GETARG_INT32(0);
+ 
+ 	if ((beentry = pgstat_fetch_stat_beentry(beid)) == NULL)
+ 		PG_RETURN_NULL();
+ 
+ 	PG_RETURN_OID(beentry->databaseid);
+ }
+ 
+ 
+ Datum
+ pg_stat_get_backend_userid(PG_FUNCTION_ARGS)
+ {
+ 	PgStat_StatBeEntry	   *beentry;
+ 	int32					beid;
+ 
+ 	beid  = PG_GETARG_INT32(0);
+ 
+ 	if ((beentry = pgstat_fetch_stat_beentry(beid)) == NULL)
+ 		PG_RETURN_NULL();
+ 
+ 	PG_RETURN_OID(beentry->userid);
+ }
+ 
+ 
+ Datum
+ pg_stat_get_backend_activity(PG_FUNCTION_ARGS)
+ {
+ 	PgStat_StatBeEntry	   *beentry;
+ 	int32					beid;
+ 	int						len;
+ 	text				   *result;
+ 
+ 	if (!superuser())
+ 		PG_RETURN_NULL();
+ 
+ 	beid  = PG_GETARG_INT32(0);
+ 
+ 	if ((beentry = pgstat_fetch_stat_beentry(beid)) == NULL)
+ 		PG_RETURN_NULL();
+ 
+ 	len = strlen(beentry->activity);
+ 	result = palloc(VARHDRSZ + len);
+ 	VARATT_SIZEP(result) = VARHDRSZ + len;
+ 	memcpy(VARDATA(result), beentry->activity, len);
+ 
+ 	PG_RETURN_TEXT_P(result);
+ }
+ 
+ 
+ Datum
+ pg_stat_get_db_numbackends(PG_FUNCTION_ARGS)
+ {
+ 	PgStat_StatDBEntry	   *dbentry;
+ 	Oid						dbid;
+ 	int32					result;
+ 
+ 	dbid  = PG_GETARG_OID(0);
+ 
+ 	if ((dbentry = pgstat_fetch_stat_dbentry(dbid)) == NULL)
+ 		result = 0;
+ 	else
+ 		result = (int32)(dbentry->n_backends);
+ 
+ 	PG_RETURN_INT32(result);
+ }
+ 
+ 
+ Datum
+ pg_stat_get_db_xact_commit(PG_FUNCTION_ARGS)
+ {
+ 	PgStat_StatDBEntry	   *dbentry;
+ 	Oid						dbid;
+ 	int64					result;
+ 
+ 	dbid  = PG_GETARG_OID(0);
+ 
+ 	if ((dbentry = pgstat_fetch_stat_dbentry(dbid)) == NULL)
+ 		result = 0;
+ 	else
+ 		result = (int64)(dbentry->n_xact_commit);
+ 
+ 	PG_RETURN_INT64(result);
+ }
+ 
+ 
+ Datum
+ pg_stat_get_db_xact_rollback(PG_FUNCTION_ARGS)
+ {
+ 	PgStat_StatDBEntry	   *dbentry;
+ 	Oid						dbid;
+ 	int64					result;
+ 
+ 	dbid  = PG_GETARG_OID(0);
+ 
+ 	if ((dbentry = pgstat_fetch_stat_dbentry(dbid)) == NULL)
+ 		result = 0;
+ 	else
+ 		result = (int64)(dbentry->n_xact_rollback);
+ 
+ 	PG_RETURN_INT64(result);
+ }
+ 
+ 
+ Datum
+ pg_stat_get_db_blocks_fetched(PG_FUNCTION_ARGS)
+ {
+ 	PgStat_StatDBEntry	   *dbentry;
+ 	Oid						dbid;
+ 	int64					result;
+ 
+ 	dbid  = PG_GETARG_OID(0);
+ 
+ 	if ((dbentry = pgstat_fetch_stat_dbentry(dbid)) == NULL)
+ 		result = 0;
+ 	else
+ 		result = (int64)(dbentry->n_blocks_fetched);
+ 
+ 	PG_RETURN_INT64(result);
+ }
+ 
+ 
+ Datum
+ pg_stat_get_db_blocks_hit(PG_FUNCTION_ARGS)
+ {
+ 	PgStat_StatDBEntry	   *dbentry;
+ 	Oid						dbid;
+ 	int64					result;
+ 
+ 	dbid  = PG_GETARG_OID(0);
+ 
+ 	if ((dbentry = pgstat_fetch_stat_dbentry(dbid)) == NULL)
+ 		result = 0;
+ 	else
+ 		result = (int64)(dbentry->n_blocks_hit);
+ 
+ 	PG_RETURN_INT64(result);
+ }
+ 
+ 
diff -crN ../src.orig/backend/utils/adt/regproc.c ./backend/utils/adt/regproc.c
*** ../src.orig/backend/utils/adt/regproc.c	Sat Jun  2 11:17:49 2001
--- ./backend/utils/adt/regproc.c	Sat Jun  2 11:18:33 2001
***************
*** 90,96 ****
  				tuple.t_self = indexRes->heap_iptr;
  				heap_fetch(hdesc, SnapshotNow,
  						   &tuple,
! 						   &buffer);
  				pfree(indexRes);
  				if (tuple.t_data != NULL)
  				{
--- 90,97 ----
  				tuple.t_self = indexRes->heap_iptr;
  				heap_fetch(hdesc, SnapshotNow,
  						   &tuple,
! 						   &buffer,
! 						   sd);
  				pfree(indexRes);
  				if (tuple.t_data != NULL)
  				{
diff -crN ../src.orig/backend/utils/cache/catcache.c ./backend/utils/cache/catcache.c
*** ../src.orig/backend/utils/cache/catcache.c	Sat Jun  2 11:17:49 2001
--- ./backend/utils/cache/catcache.c	Sat Jun  2 11:18:33 2001
***************
*** 915,921 ****
  		while ((indexRes = index_getnext(isd, ForwardScanDirection)))
  		{
  			tuple.t_self = indexRes->heap_iptr;
! 			heap_fetch(relation, SnapshotNow, &tuple, &buffer);
  			pfree(indexRes);
  			if (tuple.t_data != NULL)
  			{
--- 915,921 ----
  		while ((indexRes = index_getnext(isd, ForwardScanDirection)))
  		{
  			tuple.t_self = indexRes->heap_iptr;
! 			heap_fetch(relation, SnapshotNow, &tuple, &buffer, isd);
  			pfree(indexRes);
  			if (tuple.t_data != NULL)
  			{
diff -crN ../src.orig/backend/utils/cache/relcache.c ./backend/utils/cache/relcache.c
*** ../src.orig/backend/utils/cache/relcache.c	Sat Jun  2 11:17:49 2001
--- ./backend/utils/cache/relcache.c	Sat Jun  2 11:18:33 2001
***************
*** 2263,2269 ****
  				break;
  
  			tuple.t_self = indexRes->heap_iptr;
! 			heap_fetch(adrel, SnapshotNow, &tuple, &buffer);
  			pfree(indexRes);
  			if (tuple.t_data == NULL)
  				continue;
--- 2263,2269 ----
  				break;
  
  			tuple.t_self = indexRes->heap_iptr;
! 			heap_fetch(adrel, SnapshotNow, &tuple, &buffer, sd);
  			pfree(indexRes);
  			if (tuple.t_data == NULL)
  				continue;
***************
*** 2370,2376 ****
  				break;
  
  			tuple.t_self = indexRes->heap_iptr;
! 			heap_fetch(rcrel, SnapshotNow, &tuple, &buffer);
  			pfree(indexRes);
  			if (tuple.t_data == NULL)
  				continue;
--- 2370,2376 ----
  				break;
  
  			tuple.t_self = indexRes->heap_iptr;
! 			heap_fetch(rcrel, SnapshotNow, &tuple, &buffer, sd);
  			pfree(indexRes);
  			if (tuple.t_data == NULL)
  				continue;
***************
*** 2508,2514 ****
  			tuple.t_self = indexRes->heap_iptr;
  			tuple.t_datamcxt = NULL;
  			tuple.t_data = NULL;
! 			heap_fetch(indrel, SnapshotNow, &tuple, &buffer);
  			pfree(indexRes);
  			if (tuple.t_data == NULL)
  				continue;
--- 2508,2514 ----
  			tuple.t_self = indexRes->heap_iptr;
  			tuple.t_datamcxt = NULL;
  			tuple.t_data = NULL;
! 			heap_fetch(indrel, SnapshotNow, &tuple, &buffer, sd);
  			pfree(indexRes);
  			if (tuple.t_data == NULL)
  				continue;
diff -crN ../src.orig/backend/utils/hash/dynahash.c ./backend/utils/hash/dynahash.c
*** ../src.orig/backend/utils/hash/dynahash.c	Sat Jun  2 11:17:49 2001
--- ./backend/utils/hash/dynahash.c	Sat Jun  2 11:18:33 2001
***************
*** 82,99 ****
   * ----------------
   */
  static MemoryContext DynaHashCxt = NULL;
  
  static void *
  DynaHashAlloc(Size size)
  {
! 	if (!DynaHashCxt)
! 		DynaHashCxt = AllocSetContextCreate(TopMemoryContext,
! 											"DynaHash",
! 											ALLOCSET_DEFAULT_MINSIZE,
! 											ALLOCSET_DEFAULT_INITSIZE,
! 											ALLOCSET_DEFAULT_MAXSIZE);
! 
! 	return MemoryContextAlloc(DynaHashCxt, size);
  }
  
  #define MEM_ALLOC		DynaHashAlloc
--- 82,94 ----
   * ----------------
   */
  static MemoryContext DynaHashCxt = NULL;
+ static MemoryContext CurrentDynaHashCxt = NULL;
  
  static void *
  DynaHashAlloc(Size size)
  {
! 	Assert(MemoryContextIsValid(CurrentDynaHashCxt));
! 	return MemoryContextAlloc(CurrentDynaHashCxt, size);
  }
  
  #define MEM_ALLOC		DynaHashAlloc
***************
*** 133,138 ****
--- 128,144 ----
  	HHDR	   *hctl;
  	HTAB	   *hashp;
  
+ 	if (!DynaHashCxt)
+ 		DynaHashCxt = AllocSetContextCreate(TopMemoryContext,
+ 											"DynaHash",
+ 											ALLOCSET_DEFAULT_MINSIZE,
+ 											ALLOCSET_DEFAULT_INITSIZE,
+ 											ALLOCSET_DEFAULT_MAXSIZE);
+ 
+ 	if (flags & HASH_CONTEXT)
+ 		CurrentDynaHashCxt = info->hcxt;
+ 	else
+ 		CurrentDynaHashCxt = DynaHashCxt;
  
  	hashp = (HTAB *) MEM_ALLOC(sizeof(HTAB));
  	MemSet(hashp, 0, sizeof(HTAB));
***************
*** 157,162 ****
--- 163,169 ----
  		hashp->segbase = (char *) info->segbase;
  		hashp->alloc = info->alloc;
  		hashp->dir = (SEG_OFFSET *) info->dir;
+ 		hashp->hcxt = NULL;
  
  		/* hash table already exists, we're just attaching to it */
  		if (flags & HASH_ATTACH)
***************
*** 171,182 ****
  		hashp->alloc = MEM_ALLOC;
  		hashp->dir = NULL;
  		hashp->segbase = NULL;
  
  	}
  
  	if (!hashp->hctl)
  	{
! 		hashp->hctl = (HHDR *) hashp->alloc(sizeof(HHDR));
  		if (!hashp->hctl)
  			return 0;
  	}
--- 178,190 ----
  		hashp->alloc = MEM_ALLOC;
  		hashp->dir = NULL;
  		hashp->segbase = NULL;
+ 		hashp->hcxt = DynaHashCxt;
  
  	}
  
  	if (!hashp->hctl)
  	{
! 			hashp->hctl = (HHDR *) hashp->alloc(sizeof(HHDR));
  		if (!hashp->hctl)
  			return 0;
  	}
***************
*** 218,223 ****
--- 226,253 ----
  	}
  	if (flags & HASH_ALLOC)
  		hashp->alloc = info->alloc;
+ 	else
+ 	{
+ 		if (flags & HASH_CONTEXT)
+ 		{
+ 			CurrentDynaHashCxt = AllocSetContextCreate(info->hcxt,
+ 											"DynaHashTable",
+ 											ALLOCSET_DEFAULT_MINSIZE,
+ 											ALLOCSET_DEFAULT_INITSIZE,
+ 											ALLOCSET_DEFAULT_MAXSIZE);
+ 			
+ 			hashp->hcxt = CurrentDynaHashCxt;
+ 		}
+ 		else
+ 		{
+ 			CurrentDynaHashCxt = AllocSetContextCreate(DynaHashCxt,
+ 											"DynaHashTable",
+ 											ALLOCSET_DEFAULT_MINSIZE,
+ 											ALLOCSET_DEFAULT_INITSIZE,
+ 											ALLOCSET_DEFAULT_MAXSIZE);
+ 			hashp->hcxt = CurrentDynaHashCxt;
+ 		}
+ 	}
  
  	if (init_htab(hashp, nelem))
  	{
***************
*** 305,310 ****
--- 335,341 ----
  	/* Allocate a directory */
  	if (!(hashp->dir))
  	{
+ 		CurrentDynaHashCxt = hashp->hcxt;
  		hashp->dir = (SEG_OFFSET *)
  			hashp->alloc(hctl->dsize * sizeof(SEG_OFFSET));
  		if (!hashp->dir)
***************
*** 414,419 ****
--- 445,458 ----
   * allocated individually, see bucket_alloc!!  Why doesn't it crash?
   * ANSWER: it probably does crash, but is never invoked in normal
   * operations...
+  *
+  * Thomas is right, it does crash. Therefore I changed the code
+  * to use a separate memory context which is a child of the DynaHashCxt
+  * by default. And the HASHCTL structure got extended with a hcxt
+  * field, where someone can specify an explicit context (giving new
+  * flag HASH_CONTEXT) and forget about hash_destroy() completely.
+  * The shmem operations aren't changed, but in shmem mode a destroy
+  * doesn't work anyway. Jan Wieck 03/2001.
   */
  
  void
***************
*** 421,426 ****
--- 460,466 ----
  {
  	if (hashp != NULL)
  	{
+ #if 0
  		SEG_OFFSET	segNum;
  		SEGMENT		segp;
  		int			nsegs = hashp->hctl->nsegs;
***************
*** 429,442 ****
--- 469,495 ----
  					p,
  					q;
  		ELEMENT    *curr;
+ #endif
  
  		/* cannot destroy a shared memory hash table */
  		Assert(!hashp->segbase);
  		/* allocation method must be one we know how to free, too */
  		Assert(hashp->alloc == MEM_ALLOC);
+ 		/* so this hashtable must have it's own context */
+ 		Assert(hashp->hcxt != NULL);
  
  		hash_stats("destroy", hashp);
  
+ 		/*
+ 		 * Free buckets, dir etc. by destroying the hash tables
+ 		 * memory context.
+ 		 */
+ 		MemoryContextDelete(hashp->hcxt);
+ 
+ #if 0
+ 		/*
+ 		 * Dead code - replaced by MemoryContextDelete() above
+ 		 */
  		for (segNum = 0; nsegs > 0; nsegs--, segNum++)
  		{
  
***************
*** 453,458 ****
--- 506,518 ----
  			MEM_FREE((char *) segp);
  		}
  		MEM_FREE((char *) hashp->dir);
+ #endif
+ 
+ 		/*
+ 		 * Free the HTAB and control structure, which are allocated
+ 		 * in the parent context (DynaHashCxt or the context given
+ 		 * by the caller of hash_create().
+ 		 */
  		MEM_FREE((char *) hashp->hctl);
  		MEM_FREE((char *) hashp);
  	}
***************
*** 876,881 ****
--- 936,942 ----
  	old_dirsize = hashp->hctl->dsize * sizeof(SEG_OFFSET);
  	new_dirsize = new_dsize * sizeof(SEG_OFFSET);
  
+ 	CurrentDynaHashCxt = hashp->hcxt;
  	old_p = (char *) hashp->dir;
  	p = (char *) hashp->alloc((Size) new_dirsize);
  
***************
*** 898,903 ****
--- 959,965 ----
  	SEGMENT		segp;
  	SEG_OFFSET	segOffset;
  
+ 	CurrentDynaHashCxt = hashp->hcxt;
  	segp = (SEGMENT) hashp->alloc(sizeof(BUCKET_INDEX) * hashp->hctl->ssize);
  
  	if (!segp)
***************
*** 928,933 ****
--- 990,996 ----
  	/* make sure its aligned correctly */
  	bucketSize = MAXALIGN(bucketSize);
  
+ 	CurrentDynaHashCxt = hashp->hcxt;
  	tmpBucket = (ELEMENT *) hashp->alloc(BUCKET_ALLOC_INCR * bucketSize);
  
  	if (!tmpBucket)
diff -crN ../src.orig/bin/initdb/initdb.sh ./bin/initdb/initdb.sh
*** ../src.orig/bin/initdb/initdb.sh	Sat Jun  2 11:17:50 2001
--- ./bin/initdb/initdb.sh	Sat Jun  2 11:32:47 2001
***************
*** 612,617 ****
--- 612,781 ----
              AND I.oid = X.indexrelid;" \
          | "$PGPATH"/postgres $PGSQL_OPT template1 > /dev/null || exit_nicely
  
+ echo "CREATE VIEW pg_stat_all_tables AS \
+ 		SELECT \
+ 			C.oid AS relid, \
+ 			C.relname AS relname, \
+ 			pg_stat_get_numscans(C.oid) AS seq_scan, \
+ 			pg_stat_get_tuples_returned(C.oid) AS seq_tup_read, \
+ 			sum(pg_stat_get_numscans(I.indexrelid)) AS idx_scan, \
+ 			sum(pg_stat_get_tuples_fetched(I.indexrelid)) AS idx_tup_fetch, \
+ 			pg_stat_get_tuples_inserted(C.oid) AS n_tup_ins, \
+ 			pg_stat_get_tuples_updated(C.oid) AS n_tup_upd, \
+ 			pg_stat_get_tuples_deleted(C.oid) AS n_tup_del \
+ 		FROM pg_class C FULL OUTER JOIN \
+ 		     pg_index I ON C.oid = I.indrelid \
+ 		WHERE C.relkind = 'r' \
+ 		GROUP BY C.oid, C.relname;" \
+         | "$PGPATH"/postgres $PGSQL_OPT template1 > /dev/null || exit_nicely
+ 
+ echo "CREATE VIEW pg_stat_sys_tables AS \
+ 		SELECT * FROM pg_stat_all_tables \
+ 		WHERE relname ~ '^pg_';" \
+         | "$PGPATH"/postgres $PGSQL_OPT template1 > /dev/null || exit_nicely
+ 
+ echo "CREATE VIEW pg_stat_user_tables AS \
+ 		SELECT * FROM pg_stat_all_tables \
+ 		WHERE relname !~ '^pg_';" \
+         | "$PGPATH"/postgres $PGSQL_OPT template1 > /dev/null || exit_nicely
+ 
+ echo "CREATE VIEW pg_statio_all_tables AS \
+ 		SELECT \
+ 			C.oid AS relid, \
+ 			C.relname AS relname, \
+ 			pg_stat_get_blocks_fetched(C.oid) - \
+ 				pg_stat_get_blocks_hit(C.oid) AS heap_blks_read, \
+ 			pg_stat_get_blocks_hit(C.oid) AS heap_blks_hit, \
+ 			sum(pg_stat_get_blocks_fetched(I.indexrelid) - \
+ 				pg_stat_get_blocks_hit(I.indexrelid)) AS idx_blks_read, \
+ 			sum(pg_stat_get_blocks_hit(I.indexrelid)) AS idx_blks_hit, \
+ 			pg_stat_get_blocks_fetched(T.oid) - \
+ 				pg_stat_get_blocks_hit(T.oid) AS toast_blks_read, \
+ 			pg_stat_get_blocks_hit(T.oid) AS toast_blks_hit, \
+ 			pg_stat_get_blocks_fetched(X.oid) - \
+ 				pg_stat_get_blocks_hit(X.oid) AS tidx_blks_read, \
+ 			pg_stat_get_blocks_hit(X.oid) AS tidx_blks_hit \
+ 		FROM pg_class C FULL OUTER JOIN \
+ 			pg_index I ON C.oid = I.indrelid FULL OUTER JOIN \
+ 			pg_class T ON C.reltoastrelid = T.oid FULL OUTER JOIN \
+ 			pg_class X ON C.reltoastidxid = X.oid \
+ 		WHERE C.relkind = 'r' \
+ 		GROUP BY C.oid, C.relname, T.oid, X.oid;" \
+         | "$PGPATH"/postgres $PGSQL_OPT template1 > /dev/null || exit_nicely
+ 
+ echo "CREATE VIEW pg_statio_sys_tables AS \
+ 		SELECT * FROM pg_statio_all_tables \
+ 		WHERE relname ~ '^pg_';" \
+         | "$PGPATH"/postgres $PGSQL_OPT template1 > /dev/null || exit_nicely
+ 
+ echo "CREATE VIEW pg_statio_user_tables AS \
+ 		SELECT * FROM pg_statio_all_tables \
+ 		WHERE relname !~ '^pg_';" \
+         | "$PGPATH"/postgres $PGSQL_OPT template1 > /dev/null || exit_nicely
+ 
+ echo "CREATE VIEW pg_stat_all_indexes AS \
+ 		SELECT \
+ 			C.oid AS relid, \
+ 			I.oid AS indexrelid, \
+ 			C.relname AS relname, \
+ 			I.relname AS indexrelname, \
+ 			pg_stat_get_numscans(I.oid) AS idx_scan, \
+ 			pg_stat_get_tuples_returned(I.oid) AS idx_tup_read, \
+ 			pg_stat_get_tuples_fetched(I.oid) AS idx_tup_fetch \
+ 		FROM pg_class C, \
+ 			pg_class I, \
+ 			pg_index X \
+ 		WHERE C.relkind = 'r' AND \
+ 			X.indrelid = C.oid AND \
+ 			X.indexrelid = I.oid;" \
+         | "$PGPATH"/postgres $PGSQL_OPT template1 > /dev/null || exit_nicely
+ 
+ echo "CREATE VIEW pg_stat_sys_indexes AS \
+ 		SELECT * FROM pg_stat_all_indexes \
+ 		WHERE relname ~ '^pg_';" \
+         | "$PGPATH"/postgres $PGSQL_OPT template1 > /dev/null || exit_nicely
+ 
+ echo "CREATE VIEW pg_stat_user_indexes AS \
+ 		SELECT * FROM pg_stat_all_indexes \
+ 		WHERE relname !~ '^pg_';" \
+         | "$PGPATH"/postgres $PGSQL_OPT template1 > /dev/null || exit_nicely
+ 
+ echo "CREATE VIEW pg_statio_all_indexes AS \
+ 		SELECT \
+ 			C.oid AS relid, \
+ 			I.oid AS indexrelid, \
+ 			C.relname AS relname, \
+ 			I.relname AS indexrelname, \
+ 			pg_stat_get_blocks_fetched(I.oid) - \
+ 				pg_stat_get_blocks_hit(I.oid) AS idx_blks_read, \
+ 			pg_stat_get_blocks_hit(I.oid) AS idx_blks_hit \
+ 		FROM pg_class C, \
+ 			pg_class I, \
+ 			pg_index X \
+ 		WHERE C.relkind = 'r' AND \
+ 			X.indrelid = C.oid AND \
+ 			X.indexrelid = I.oid;" \
+         | "$PGPATH"/postgres $PGSQL_OPT template1 > /dev/null || exit_nicely
+ 
+ echo "CREATE VIEW pg_statio_sys_indexes AS \
+ 		SELECT * FROM pg_statio_all_indexes \
+ 		WHERE relname ~ '^pg_';" \
+         | "$PGPATH"/postgres $PGSQL_OPT template1 > /dev/null || exit_nicely
+ 
+ echo "CREATE VIEW pg_statio_user_indexes AS \
+ 		SELECT * FROM pg_statio_all_indexes \
+ 		WHERE relname !~ '^pg_';" \
+         | "$PGPATH"/postgres $PGSQL_OPT template1 > /dev/null || exit_nicely
+ 
+ echo "CREATE VIEW pg_statio_all_sequences AS \
+ 		SELECT \
+ 			C.oid AS relid, \
+ 			C.relname AS relname, \
+ 			pg_stat_get_blocks_fetched(C.oid) - \
+ 				pg_stat_get_blocks_hit(C.oid) AS blks_read, \
+ 			pg_stat_get_blocks_hit(C.oid) AS blks_hit \
+ 		FROM pg_class C \
+ 		WHERE C.relkind = 'S';" \
+         | "$PGPATH"/postgres $PGSQL_OPT template1 > /dev/null || exit_nicely
+ 
+ echo "CREATE VIEW pg_statio_sys_sequences AS \
+ 		SELECT * FROM pg_statio_all_sequences \
+ 		WHERE relname ~ '^pg_';" \
+         | "$PGPATH"/postgres $PGSQL_OPT template1 > /dev/null || exit_nicely
+ 
+ echo "CREATE VIEW pg_statio_user_sequences AS \
+ 		SELECT * FROM pg_statio_all_sequences \
+ 		WHERE relname !~ '^pg_';" \
+         | "$PGPATH"/postgres $PGSQL_OPT template1 > /dev/null || exit_nicely
+ 
+ echo "CREATE VIEW pg_stat_activity AS \
+ 		SELECT \
+ 			D.oid AS datid, \
+ 			D.datname AS datname, \
+ 			pg_stat_get_backend_pid(S.backendid) AS procpid, \
+ 			pg_stat_get_backend_userid(S.backendid) AS usesysid, \
+ 			U.usename AS usename, \
+ 			pg_stat_get_backend_activity(S.backendid) AS current_query \
+ 		FROM pg_database D, \
+ 			(SELECT pg_stat_get_backend_idset() AS backendid) AS S, \
+ 			pg_shadow U \
+ 		WHERE pg_stat_get_backend_dbid(S.backendid) = D.oid AND \
+ 			pg_stat_get_backend_userid(S.backendid) = U.usesysid;" \
+         | "$PGPATH"/postgres $PGSQL_OPT template1 > /dev/null || exit_nicely
+ 
+ echo "CREATE VIEW pg_stat_database AS \
+ 		SELECT \
+ 			D.oid AS datid, \
+ 			D.datname AS 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 \
+ 		FROM pg_database D;" \
+         | "$PGPATH"/postgres $PGSQL_OPT template1 > /dev/null || exit_nicely
+ 
  echo "Loading pg_description."
  echo "COPY pg_description FROM STDIN" > $TEMPFILE
  cat "$TEMPLATE1_DESCR" >> $TEMPFILE
diff -crN ../src.orig/include/access/heapam.h ./include/access/heapam.h
*** ../src.orig/include/access/heapam.h	Sat Jun  2 11:17:50 2001
--- ./include/access/heapam.h	Sat Jun  2 11:18:33 2001
***************
*** 202,208 ****
  extern void heap_rescan(HeapScanDesc scan, bool scanFromEnd, ScanKey key);
  extern void heap_endscan(HeapScanDesc scan);
  extern HeapTuple heap_getnext(HeapScanDesc scandesc, int backw);
! extern void heap_fetch(Relation relation, Snapshot snapshot, HeapTuple tup, Buffer *userbuf);
  extern ItemPointer heap_get_latest_tid(Relation relation, Snapshot snapshot, ItemPointer tid);
  extern Oid	heap_insert(Relation relation, HeapTuple tup);
  extern int	heap_delete(Relation relation, ItemPointer tid, ItemPointer ctid);
--- 202,208 ----
  extern void heap_rescan(HeapScanDesc scan, bool scanFromEnd, ScanKey key);
  extern void heap_endscan(HeapScanDesc scan);
  extern HeapTuple heap_getnext(HeapScanDesc scandesc, int backw);
! extern void heap_fetch(Relation relation, Snapshot snapshot, HeapTuple tup, Buffer *userbuf, IndexScanDesc iscan);
  extern ItemPointer heap_get_latest_tid(Relation relation, Snapshot snapshot, ItemPointer tid);
  extern Oid	heap_insert(Relation relation, HeapTuple tup);
  extern int	heap_delete(Relation relation, ItemPointer tid, ItemPointer ctid);
diff -crN ../src.orig/include/access/relscan.h ./include/access/relscan.h
*** ../src.orig/include/access/relscan.h	Sat Jun  2 11:17:50 2001
--- ./include/access/relscan.h	Sat Jun  2 11:18:33 2001
***************
*** 36,41 ****
--- 36,43 ----
  	uint16		rs_cdelta;		/* current delta in chain */
  	uint16		rs_nkeys;		/* number of attributes in keys */
  	ScanKey		rs_key;			/* key descriptors */
+ 
+ 	PgStat_Info rs_pgstat_info;	/* statistics collector hook */
  } HeapScanDescData;
  
  typedef HeapScanDescData *HeapScanDesc;
***************
*** 55,60 ****
--- 57,64 ----
  	uint16		numberOfKeys;	/* number of key attributes */
  	ScanKey		keyData;		/* key descriptor */
  	FmgrInfo	fn_getnext;		/* cached lookup info for am's getnext fn */
+ 
+ 	PgStat_Info xs_pgstat_info;	/* statistics collector hook */
  } IndexScanDescData;
  
  typedef IndexScanDescData *IndexScanDesc;
diff -crN ../src.orig/include/catalog/pg_proc.h ./include/catalog/pg_proc.h
*** ../src.orig/include/catalog/pg_proc.h	Sat Jun  2 11:17:50 2001
--- ./include/catalog/pg_proc.h	Sat Jun  2 11:18:33 2001
***************
*** 2614,2619 ****
--- 2614,2656 ----
  DATA(insert OID = 1909 (  int8shr		   PGUID 12 f t t t 2 f 20 "20 23" 100 0 0 100	int8shr - ));
  DESCR("binary shift right");
  
+ DATA(insert OID = 1910 (  pg_stat_get_numscans			PGUID 12 f t t t 1 f 20 "26" 100 0 0 100	pg_stat_get_numscans - ));
+ DESCR("Statistics: Number of scans done for table/index");
+ DATA(insert OID = 1911 (  pg_stat_get_tuples_returned	PGUID 12 f t t t 1 f 20 "26" 100 0 0 100	pg_stat_get_tuples_returned - ));
+ DESCR("Statistics: Number of tuples read by seqscan");
+ DATA(insert OID = 1912 (  pg_stat_get_tuples_fetched	PGUID 12 f t t t 1 f 20 "26" 100 0 0 100	pg_stat_get_tuples_fetched - ));
+ DESCR("Statistics: Number of tuples fetched by idxscan");
+ DATA(insert OID = 1913 (  pg_stat_get_tuples_inserted	PGUID 12 f t t t 1 f 20 "26" 100 0 0 100	pg_stat_get_tuples_inserted - ));
+ DESCR("Statistics: Number of tuples inserted");
+ DATA(insert OID = 1914 (  pg_stat_get_tuples_updated	PGUID 12 f t t t 1 f 20 "26" 100 0 0 100	pg_stat_get_tuples_updated - ));
+ DESCR("Statistics: Number of tuples updated");
+ DATA(insert OID = 1915 (  pg_stat_get_tuples_deleted	PGUID 12 f t t t 1 f 20 "26" 100 0 0 100	pg_stat_get_tuples_deleted - ));
+ DESCR("Statistics: Number of tuples deleted");
+ DATA(insert OID = 1916 (  pg_stat_get_blocks_fetched	PGUID 12 f t t t 1 f 20 "26" 100 0 0 100	pg_stat_get_blocks_fetched - ));
+ DESCR("Statistics: Number of blocks fetched");
+ DATA(insert OID = 1917 (  pg_stat_get_blocks_hit		PGUID 12 f t t t 1 f 20 "26" 100 0 0 100	pg_stat_get_blocks_hit - ));
+ DESCR("Statistics: Number of blocks found in cache");
+ DATA(insert OID = 1918 (  pg_stat_get_backend_idset		PGUID 12 f t t t 0 t 23 "" 100 0 0 100	pg_stat_get_backend_idset - ));
+ DESCR("Statistics: Currently active backend IDs");
+ DATA(insert OID = 1919 (  pg_stat_get_backend_pid		PGUID 12 f t t t 1 f 23 "23" 100 0 0 100	pg_stat_get_backend_pid - ));
+ DESCR("Statistics: PID of backend");
+ DATA(insert OID = 1920 (  pg_stat_get_backend_dbid		PGUID 12 f t t t 1 f 26 "23" 100 0 0 100	pg_stat_get_backend_dbid - ));
+ DESCR("Statistics: Database ID of backend");
+ DATA(insert OID = 1921 (  pg_stat_get_backend_userid	PGUID 12 f t t t 1 f 26 "23" 100 0 0 100	pg_stat_get_backend_userid - ));
+ DESCR("Statistics: User ID of backend");
+ DATA(insert OID = 1922 (  pg_stat_get_backend_activity	PGUID 12 f t t t 1 f 25 "23" 100 0 0 100	pg_stat_get_backend_activity - ));
+ DESCR("Statistics: Current query of backend");
+ DATA(insert OID = 1923 (  pg_stat_get_db_numbackends	PGUID 12 f t t t 1 f 23 "26" 100 0 0 100	pg_stat_get_db_numbackends - ));
+ DESCR("Statistics: Number of backends in database");
+ DATA(insert OID = 1924 (  pg_stat_get_db_xact_commit	PGUID 12 f t t t 1 f 20 "26" 100 0 0 100	pg_stat_get_db_xact_commit - ));
+ DESCR("Statistics: Transactions committed");
+ DATA(insert OID = 1925 (  pg_stat_get_db_xact_rollback	PGUID 12 f t t t 1 f 20 "26" 100 0 0 100	pg_stat_get_db_xact_rollback - ));
+ DESCR("Statistics: Transactions rolled back");
+ DATA(insert OID = 1926 (  pg_stat_get_db_blocks_fetched	PGUID 12 f t t t 1 f 20 "26" 100 0 0 100	pg_stat_get_db_blocks_fetched - ));
+ DESCR("Statistics: Blocks fetched for database");
+ DATA(insert OID = 1927 (  pg_stat_get_db_blocks_hit		PGUID 12 f t t t 1 f 20 "26" 100 0 0 100	pg_stat_get_db_blocks_hit - ));
+ DESCR("Statistics: Block found in cache for database");
+ 
  /*
   * prototypes for functions pg_proc.c
   */
diff -crN ../src.orig/include/pgstat.h ./include/pgstat.h
*** ../src.orig/include/pgstat.h	Wed Dec 31 19:00:00 1969
--- ./include/pgstat.h	Sat Jun  2 11:18:33 2001
***************
*** 0 ****
--- 1,380 ----
+ /* ----------
+  *	pgstat.h
+  *
+  *	Definitions for the PostgreSQL statistics collector daemon.
+  *
+  *	Copyright (c) 2001, PostgreSQL Global Development Group
+  *
+  *  $Id:$
+  * ----------
+  */
+ #ifndef PGSTAT_H
+ #define PGSTAT_H
+ 
+ /* ----------
+  * Paths for the statistics files. The %s is replaced with the
+  * installations $PGDATA.
+  * ----------
+  */
+ #define	PGSTAT_STAT_FILENAME	"%s/pgstat.stat"
+ #define	PGSTAT_STAT_TMPFILE		"%s/pgstat.tmp.%d"
+ 
+ /* ----------
+  * Timer definitions.
+  * ----------
+  */
+ #define PGSTAT_STAT_INTERVAL	500		/* How often to write the status	*/
+ 										/* file in milliseconds.			*/
+ 
+ #define PGSTAT_DESTROY_DELAY	10000	/* How long to keep destroyed		*/
+ 										/* objects known to give delayed	*/
+ 										/* UDP packets time to arrive		*/
+ 										/* in milliseconds.					*/
+ 
+ #define PGSTAT_DESTROY_COUNT	(PGSTAT_DESTROY_DELAY 					\
+ 								/ PGSTAT_STAT_INTERVAL)
+ 
+ 
+ /* ----------
+  * How much of the actual query to send to the collector.
+  * ----------
+  */
+ #define PGSTAT_ACTIVITY_SIZE	256
+ 
+ 
+ /* ----------
+  * The types of backend/postmaster -> collector messages
+  * ----------
+  */
+ #define	PGSTAT_MTYPE_DUMMY			0
+ #define	PGSTAT_MTYPE_BESTART		1
+ #define	PGSTAT_MTYPE_BETERM			2
+ #define	PGSTAT_MTYPE_ACTIVITY		3
+ #define	PGSTAT_MTYPE_TABSTAT		4
+ #define	PGSTAT_MTYPE_TABPURGE		5
+ #define	PGSTAT_MTYPE_DROPDB			6
+ #define	PGSTAT_MTYPE_RESETCOUNTER	7
+ 
+ /* ----------
+  * TODO
+  * For simplicity now, the number of messages buffered in
+  * pgstat_recvbuffer(). Should be an amount of bytes used
+  * for a gapless wraparound buffer.
+  * ----------
+  */
+ #define	PGSTAT_RECVBUFFERSZ		1024
+ 
+ 
+ /* ----------
+  * The initial size hints for the hash tables used in the
+  * collector.
+  * ----------
+  */
+ #define PGSTAT_DB_HASH_SIZE		16
+ #define PGSTAT_BE_HASH_SIZE		512
+ #define PGSTAT_TAB_HASH_SIZE	512
+ 
+ 
+ /* ----------
+  * The data type used for counters.
+  * ----------
+  */
+ typedef int64	PgStat_Counter;
+ 
+ 
+ /* ------------------------------------------------------------
+  * Statistic collector data structures follow
+  * ------------------------------------------------------------
+  */
+ /* ----------
+  * PgStat_StatDBEntry			The collectors data per database
+  * ----------
+  */
+ typedef struct	PgStat_StatDBEntry
+ {
+ 	Oid				databaseid;
+ 	HTAB		   *tables;
+ 	int				n_backends;
+ 	PgStat_Counter	n_connects;
+ 	PgStat_Counter	n_xact_commit;
+ 	PgStat_Counter	n_xact_rollback;
+ 	PgStat_Counter	n_blocks_fetched;
+ 	PgStat_Counter	n_blocks_hit;
+ 	int				destroy;			
+ } PgStat_StatDBEntry;
+ 
+ 
+ /* ----------
+  * PgStat_StatBeEntry			The collectors data per backend
+  * ----------
+  */
+ typedef struct	PgStat_StatBeEntry
+ {
+ 	Oid				databaseid;
+ 	Oid				userid;
+ 	int				procpid;
+ 	char			activity[PGSTAT_ACTIVITY_SIZE];
+ } PgStat_StatBeEntry;
+ 
+ 
+ /* ----------
+  * PgStat_StatBeDead			Because UDP packets can arrive out of
+  *								order, we need to keep some information
+  *								about backends that are known to be
+  *								dead for some seconds. This info is held
+  *								in a hash table of these structs.
+  * ----------
+  */
+ typedef struct	PgStat_StatBeDead
+ {
+ 	int				procpid;
+ 	int				backendid;
+ 	int				destroy;
+ } PgStat_StatBeDead;
+ 
+ 
+ /* ----------
+  * PgStat_StatTabEntry			The collectors data table data
+  * ----------
+  */
+ typedef struct	PgStat_StatTabEntry
+ {
+ 	Oid					tableid;
+ 
+ 	PgStat_Counter		numscans;
+ 
+ 	PgStat_Counter		tuples_returned;
+ 	PgStat_Counter		tuples_fetched;
+ 	PgStat_Counter		tuples_inserted;
+ 	PgStat_Counter		tuples_updated;
+ 	PgStat_Counter		tuples_deleted;
+ 
+ 	PgStat_Counter		blocks_fetched;
+ 	PgStat_Counter		blocks_hit;
+ 
+ 	int					destroy;
+ } PgStat_StatTabEntry;
+ 
+ 
+ /* ------------------------------------------------------------
+  * Message formats follow
+  * ------------------------------------------------------------
+  */
+ 
+ 
+ /* ----------
+  * PgStat_MsgHdr				The common message header
+  * ----------
+  */
+ typedef struct	PgStat_MsgHdr
+ {
+ 	int						m_type;
+ 	int						m_size;
+ 	int						m_backendid;
+ 	int						m_procpid;
+ 	Oid						m_databaseid;
+ 	Oid						m_userid;
+ } PgStat_MsgHdr;
+ 
+ /* ----------
+  * PgStat_TabEntry				A table slot in a MsgTabstat
+  * ----------
+  */
+ typedef struct	PgStat_TableEntry
+ {
+ 	Oid						t_id;
+ 
+ 	PgStat_Counter			t_numscans;
+ 
+ 	PgStat_Counter			t_tuples_returned;
+ 	PgStat_Counter			t_tuples_fetched;
+ 	PgStat_Counter			t_tuples_inserted;
+ 	PgStat_Counter			t_tuples_updated;
+ 	PgStat_Counter			t_tuples_deleted;
+ 
+ 	PgStat_Counter			t_blocks_fetched;
+ 	PgStat_Counter			t_blocks_hit;
+ } PgStat_TableEntry;
+ 
+ 
+ /* ----------
+  * PgStat_MsgDummy				A dummy message, ignored by the collector
+  * ----------
+  */
+ typedef struct	PgStat_MsgDummy
+ {
+ 	PgStat_MsgHdr			m_hdr;
+ 	char					m_dummy[512];
+ } PgStat_MsgDummy;
+ 
+ /* ----------
+  * PgStat_MsgBestart			Sent by the backend on startup
+  * ----------
+  */
+ typedef struct	PgStat_MsgBestart
+ {
+ 	PgStat_MsgHdr			m_hdr;
+ } PgStat_MsgBestart;
+ 
+ /* ----------
+  * PgStat_MsgBeterm				Sent by the postmaster after backend exit
+  * ----------
+  */
+ typedef struct	PgStat_MsgBeterm
+ {
+ 	PgStat_MsgHdr			m_hdr;
+ } PgStat_MsgBeterm;
+ 
+ /* ----------
+  * PgStat_MsgActivity			Sent by the backends when they start
+  *								to parse a query.
+  * ----------
+  */
+ typedef struct	PgStat_MsgActivity
+ {
+ 	PgStat_MsgHdr			m_hdr;
+ 	char					m_what[PGSTAT_ACTIVITY_SIZE];
+ } PgStat_MsgActivity;
+ 
+ /* ----------
+  * How many table entries fit into a MsgTabstat. Actually,
+  * this will keep the UDP packets below 1K, what should fit
+  * unfragmented into the MTU of the lo interface on most
+  * platforms. Does anybody care for platforms where it doesn't?
+  * ----------
+  */
+ #define PGSTAT_NUM_TABENTRIES	((1000 - sizeof(PgStat_MsgHdr))			\
+ 								/ sizeof(PgStat_TableEntry))
+ 
+ /* ----------
+  * PgStat_MsgTabstat			Sent by the backend to report table
+  *								and buffer access statistics.
+  * ----------
+  */
+ typedef struct	PgStat_MsgTabstat
+ {
+ 	PgStat_MsgHdr			m_hdr;
+ 	int						m_nentries;
+ 	int						m_xact_commit;
+ 	int						m_xact_rollback;
+ 	PgStat_TableEntry		m_entry[PGSTAT_NUM_TABENTRIES];
+ } PgStat_MsgTabstat;
+ 
+ 
+ /* ----------
+  * How many Oid entries fit into a MsgTabpurge.
+  * ----------
+  */
+ #define PGSTAT_NUM_TABPURGE		((1000 - sizeof(PgStat_MsgHdr))			\
+ 								/ sizeof(PgStat_TableEntry))
+ 
+ /* ----------
+  * PgStat_MsgTabpurge			Sent by the backend to tell the collector
+  *								about dead tables.
+  * ----------
+  */
+ typedef struct	PgStat_MsgTabpurge
+ {
+ 	PgStat_MsgHdr			m_hdr;
+ 	int						m_nentries;
+ 	Oid						m_tableid[PGSTAT_NUM_TABPURGE];
+ } PgStat_MsgTabpurge;
+ 
+ 
+ /* ----------
+  * PgStat_MsgDropdb				Sent by the backend to tell the collector
+  *								about dropped database
+  * ----------
+  */
+ typedef struct	PgStat_MsgDropdb
+ {
+ 	PgStat_MsgHdr			m_hdr;
+ 	Oid						m_databaseid;
+ } PgStat_MsgDropdb;
+ 
+ 
+ /* ----------
+  * PgStat_MsgResetcounter		Sent by the backend to tell the collector
+  *								to reset counters
+  * ----------
+  */
+ typedef struct	PgStat_MsgResetcounter
+ {
+ 	PgStat_MsgHdr			m_hdr;
+ } PgStat_MsgResetcounter;
+ 
+ 
+ /* ----------
+  * PgStat_Msg					Union over all possible messages.
+  * ----------
+  */
+ typedef union	PgStat_Msg
+ {
+ 	PgStat_MsgHdr			msg_hdr;
+ 	PgStat_MsgDummy			msg_dummy;
+ 	PgStat_MsgBestart		msg_bestart;
+ 	PgStat_MsgActivity		msg_activity;
+ 	PgStat_MsgTabstat		msg_tabstat;
+ 	PgStat_MsgTabpurge		msg_tabpurge;
+ 	PgStat_MsgDropdb		msg_dropdb;
+ 	PgStat_MsgResetcounter	msg_resetcounter;
+ } PgStat_Msg;
+ 
+ 
+ 
+ 
+ /* ----------
+  * Functions called from postmaster
+  * ----------
+  */
+ extern int		pgstat_init(void);
+ extern int		pgstat_start(void);
+ extern int		pgstat_ispgstat(int pid);
+ extern void		pgstat_beterm(int pid);
+ 
+ /* ----------
+  * Functions called from backends
+  * ----------
+  */
+ extern void		pgstat_bestart(void);
+ 
+ extern void		pgstat_ping(void);
+ extern void		pgstat_report_activity(char *what);
+ extern void		pgstat_report_tabstat(void);
+ extern int		pgstat_vacuum_tabstat(void);
+ 
+ extern void		pgstat_reset_counters(void);
+ 
+ extern void		pgstat_initstats(PgStat_Info *stats, Relation rel);
+ 
+ extern void		pgstat_reset_heap_scan(PgStat_Info *stats);
+ extern void		pgstat_count_heap_scan(PgStat_Info *stats);
+ extern void		pgstat_count_heap_getnext(PgStat_Info *stats);
+ extern void		pgstat_count_heap_fetch(PgStat_Info *stats);
+ extern void		pgstat_count_heap_insert(PgStat_Info *stats);
+ extern void		pgstat_count_heap_update(PgStat_Info *stats);
+ extern void		pgstat_count_heap_delete(PgStat_Info *stats);
+ 
+ extern void		pgstat_reset_index_scan(PgStat_Info *stats);
+ extern void		pgstat_count_index_scan(PgStat_Info *stats);
+ extern void		pgstat_count_index_getnext(PgStat_Info *stats);
+ 
+ extern void		pgstat_count_buffer_read(PgStat_Info *stats,
+ 											Relation rel);
+ extern void		pgstat_count_buffer_hit(PgStat_Info *stats,
+ 											Relation rel);
+ extern void		pgstat_count_xact_commit(void);
+ extern void		pgstat_count_xact_rollback(void);
+ 
+ /* ----------
+  * Support functions for the SQL-callable functions to
+  * generate the pgstat* views.
+  * ----------
+  */
+ extern PgStat_StatDBEntry  *pgstat_fetch_stat_dbentry(Oid dbid);
+ extern PgStat_StatTabEntry *pgstat_fetch_stat_tabentry(Oid relid);
+ extern PgStat_StatBeEntry  *pgstat_fetch_stat_beentry(int beid);
+ extern int					pgstat_fetch_stat_numbackends(void);
+ 
+ 
+ #endif /* PGSTAT_H */
diff -crN ../src.orig/include/utils/hsearch.h ./include/utils/hsearch.h
*** ../src.orig/include/utils/hsearch.h	Sat Jun  2 11:17:50 2001
--- ./include/utils/hsearch.h	Sat Jun  2 11:18:33 2001
***************
*** 85,90 ****
--- 85,91 ----
  								 * pointer values */
  	SEG_OFFSET *dir;			/* 'directory' of segm starts */
  	void	   *(*alloc) (Size);/* memory allocator */
+ 	MemoryContext hcxt;			/* memory context if default allocator used */
  } HTAB;
  
  typedef struct hashctl
***************
*** 102,107 ****
--- 103,109 ----
  	long	   *dir;			/* directory if allocated already */
  	long	   *hctl;			/* location of header information in shd
  								 * mem */
+ 	MemoryContext hcxt;			/* memory context to use for all allocations */
  } HASHCTL;
  
  /* Flags to indicate action for hctl */
***************
*** 113,118 ****
--- 115,121 ----
  #define HASH_SHARED_MEM 0x040	/* Setting shared mem const */
  #define HASH_ATTACH		0x080	/* Do not initialize hctl */
  #define HASH_ALLOC		0x100	/* Setting memory allocator */
+ #define HASH_CONTEXT	0x200	/* Setting explicit memory context */
  
  
  /* seg_alloc assumes that INVALID_INDEX is 0 */
diff -crN ../src.orig/include/utils/rel.h ./include/utils/rel.h
*** ../src.orig/include/utils/rel.h	Sat Jun  2 11:17:51 2001
--- ./include/utils/rel.h	Sat Jun  2 11:18:33 2001
***************
*** 85,90 ****
--- 85,103 ----
  	int			numtriggers;
  } TriggerDesc;
  
+ 
+ /* ----------
+  * Same for the statistics collector data in Relation and scan data.
+  * ----------
+  */
+ typedef struct	PgStat_Info
+ {
+ 	void			   *tabentry;
+ 	bool				no_stats;
+ 	bool				heap_scan_counted;
+ 	bool				index_scan_counted;
+ } PgStat_Info;
+ 
  /*
   * Here are the contents of a relation cache entry.
   */
***************
*** 110,115 ****
--- 123,130 ----
  	IndexStrategy rd_istrat;	/* info needed if rel is an index */
  	RegProcedure *rd_support;
  	TriggerDesc *trigdesc;		/* Trigger info, or NULL if rel has none */
+ 
+ 	PgStat_Info	pgstat_info;
  } RelationData;
  
  typedef RelationData *Relation;
