Add contrib/pg_freespacemap to display free space map information.
authorBruce Momjian <bruce@momjian.us>
Sun, 12 Feb 2006 03:55:53 +0000 (03:55 +0000)
committerBruce Momjian <bruce@momjian.us>
Sun, 12 Feb 2006 03:55:53 +0000 (03:55 +0000)
Mark Kirkwood

contrib/Makefile
contrib/README
contrib/pg_freespacemap/Makefile [new file with mode: 0644]
contrib/pg_freespacemap/README.pg_freespacemap [new file with mode: 0644]
contrib/pg_freespacemap/pg_freespacemap.c [new file with mode: 0644]
contrib/pg_freespacemap/pg_freespacemap.sql.in [new file with mode: 0644]
src/backend/storage/freespace/freespace.c
src/include/storage/freespace.h

index 73671634406d35d7715aa87acc7f29b723b071f5..a96a77e542a8d7c4ab22dd3272409f09094196d9 100644 (file)
@@ -1,4 +1,4 @@
-# $PostgreSQL: pgsql/contrib/Makefile,v 1.61 2005/09/01 22:02:44 tgl Exp $
+# $PostgreSQL: pgsql/contrib/Makefile,v 1.62 2006/02/12 03:55:52 momjian Exp $
 
 subdir = contrib
 top_builddir = ..
@@ -21,6 +21,7 @@ WANTED_DIRS = \
                ltree           \
                oid2name        \
                pg_buffercache  \
+               pg_freespacemap \
                pg_trgm         \
                pgbench         \
                pgcrypto        \
index 17e07d7f6ae1cc01bcd58dc270423a86d1dc7fe7..82e449fe6bc97dbad8548166c113ebe7604e57b4 100644 (file)
@@ -106,6 +106,10 @@ pg_buffercache -
        Real time queries on the shared buffer cache
        by Mark Kirkwood <markir@paradise.net.nz>
 
+pg_freespacemap -
+       Displays the contents of the free space map (FSM)
+       by Mark Kirkwood <markir@paradise.net.nz>
+
 pg_trgm -
        Functions for determining the similarity of text based on trigram
        matching.
diff --git a/contrib/pg_freespacemap/Makefile b/contrib/pg_freespacemap/Makefile
new file mode 100644 (file)
index 0000000..e0e5e0c
--- /dev/null
@@ -0,0 +1,17 @@
+# $PostgreSQL: pgsql/contrib/pg_freespacemap/Makefile,v 1.1 2006/02/12 03:55:53 momjian Exp $
+
+MODULE_big = pg_freespacemap
+OBJS   = pg_freespacemap.o
+
+DATA_built = pg_freespacemap.sql 
+DOCS = README.pg_freespacemap
+
+ifdef USE_PGXS
+PGXS := $(shell pg_config --pgxs)
+include $(PGXS)
+else
+subdir = contrib/pg_freespacemap
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/pg_freespacemap/README.pg_freespacemap b/contrib/pg_freespacemap/README.pg_freespacemap
new file mode 100644 (file)
index 0000000..d171125
--- /dev/null
@@ -0,0 +1,98 @@
+Pg_freespacemap - Real time queries on the free space map (FSM).
+---------------
+
+  This module consists of a C function 'pg_freespacemap()' that returns 
+  a set of records, and a view 'pg_freespacemap' to wrapper the function.
+
+  The module provides the ability to examine the contents of the free space
+  map, without having to restart or rebuild the server with additional
+  debugging code.
+
+  By default public access is REVOKED from both of these, just in case there
+  are security issues lurking.
+
+
+Installation
+------------
+
+  Build and install the main Postgresql source, then this contrib module:
+
+  $ cd contrib/pg_freespacemap
+  $ gmake
+  $ gmake install
+
+
+  To register the functions:
+
+  $ psql -d <database> -f pg_freespacemap.sql
+
+
+Notes
+-----
+
+  The definition of the columns exposed in the view is:
+
+       Column     |  references          | Description
+  ----------------+----------------------+------------------------------------
+   blockid        |                      | Id, 1.. max_fsm_pages
+   relfilenode    | pg_class.relfilenode | Refilenode of the relation.
+   reltablespace  | pg_tablespace.oid    | Tablespace oid of the relation.
+   reldatabase    | pg_database.oid      | Database for the relation.
+   relblocknumber |                      | Offset of the page in the relation.
+   blockfreebytes |                      | Free bytes in the block/page.
+
+
+  There is one row for each page in the free space map.
+
+  Because the map is shared by all the databases, there are pages from
+  relations not belonging to the current database.
+
+  When the pg_freespacemap view is accessed, internal free space map locks are
+  taken, and a copy of the map data is made for the view to display. 
+  This ensures that the view produces a consistent set of results, while not 
+  blocking normal activity longer than necessary.  Nonetheless there 
+  could be some impact on database performance if this view is read often.
+
+
+Sample output
+-------------
+
+  regression=# \d pg_freespacemap
+      View "public.pg_freespacemap"
+      Column     |  Type   | Modifiers 
+  ---------------+---------+-----------
+  blockid        | integer | 
+  relfilenode    | oid     | 
+  reltablespace  | oid     | 
+  reldatabase    | oid     | 
+  relblocknumber | bigint  | 
+  blockfreebytes | integer | 
+ View definition:
+  SELECT p.blockid, p.relfilenode, p.reltablespace, p.reldatabase, p.relblocknumber, p.blockfreebytes
+    FROM pg_freespacemap() p(blockid integer, relfilenode oid, reltablespace oid, reldatabase oid, relblocknumber bigint, blockfreebytes integer);
+
+  regression=# SELECT c.relname, m.relblocknumber, m.blockfreebytes  
+               FROM pg_freespacemap m INNER JOIN pg_class c 
+               ON c.relfilenode = m.relfilenode LIMIT 10;
+      relname             | relblocknumber | blockfreebytes 
+  ------------------------+----------------+----------------
+  sql_features            |              5 |           2696
+  sql_implementation_info |              0 |           7104
+  sql_languages           |              0 |           8016
+  sql_packages            |              0 |           7376
+  sql_sizing              |              0 |           6032
+  pg_authid               |              0 |           7424
+  pg_toast_2618           |             13 |           4588
+  pg_toast_2618           |             12 |           1680
+  pg_toast_2618           |             10 |           1436
+  pg_toast_2618           |              7 |           1136
+  (10 rows)
+
+  regression=# 
+
+
+Author
+------
+
+  * Mark Kirkwood <markir@paradise.net.nz>
+
diff --git a/contrib/pg_freespacemap/pg_freespacemap.c b/contrib/pg_freespacemap/pg_freespacemap.c
new file mode 100644 (file)
index 0000000..a3d9a6d
--- /dev/null
@@ -0,0 +1,231 @@
+/*-------------------------------------------------------------------------
+ *
+ * pg_freespacemap.c
+ *       display some contents of the free space map.
+ *
+ *       $PostgreSQL: pgsql/contrib/pg_freespacemap/pg_freespacemap.c,v 1.1 2006/02/12 03:55:53 momjian Exp $
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+#include "funcapi.h"
+#include "catalog/pg_type.h"
+#include "storage/freespace.h"
+#include "utils/relcache.h"
+
+#define                NUM_FREESPACE_PAGES_ELEM        6
+
+#if defined(WIN32) || defined(__CYGWIN__)
+extern DLLIMPORT volatile uint32 InterruptHoldoffCount;
+#endif
+
+Datum          pg_freespacemap(PG_FUNCTION_ARGS);
+
+
+/*
+ * Record structure holding the to be exposed free space data.
+ */
+typedef struct
+{
+
+       uint32                          blockid;
+       uint32                          relfilenode;
+       uint32                          reltablespace;
+       uint32                          reldatabase;
+       uint32                          relblocknumber;
+       uint32                          blockfreebytes;
+
+}      FreeSpacePagesRec;
+
+
+/*
+ * Function context for data persisting over repeated calls.
+ */
+typedef struct
+{
+
+       AttInMetadata           *attinmeta;
+       FreeSpacePagesRec       *record;
+       char                            *values[NUM_FREESPACE_PAGES_ELEM];
+
+}      FreeSpacePagesContext;
+
+
+/*
+ * Function returning data from the Free Space Map (FSM).
+ */
+PG_FUNCTION_INFO_V1(pg_freespacemap);
+Datum
+pg_freespacemap(PG_FUNCTION_ARGS)
+{
+
+       FuncCallContext                 *funcctx;
+       Datum                                   result;
+       MemoryContext                   oldcontext;
+       FreeSpacePagesContext   *fctx;                          /* User function context. */
+       TupleDesc                               tupledesc;
+       HeapTuple                               tuple;
+
+       FSMHeader                               *FreeSpaceMap;          /* FSM main structure. */
+       FSMRelation                             *fsmrel;                        /* Individual relation. */
+
+
+       if (SRF_IS_FIRSTCALL())
+       {
+               uint32                          i;
+               uint32                          numPages;       /* Max possible no. of pages in map. */
+               int                                     nPages;         /* Mapped pages for a relation. */
+               
+               /*
+                * Get the free space map data structure.
+                */
+               FreeSpaceMap = GetFreeSpaceMap();
+
+               numPages = MaxFSMPages;
+
+               funcctx = SRF_FIRSTCALL_INIT();
+
+               /* Switch context when allocating stuff to be used in later calls */
+               oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
+               /* Construct a tuple to return. */
+               tupledesc = CreateTemplateTupleDesc(NUM_FREESPACE_PAGES_ELEM, false);
+               TupleDescInitEntry(tupledesc, (AttrNumber) 1, "blockid",
+                                                  INT4OID, -1, 0);
+               TupleDescInitEntry(tupledesc, (AttrNumber) 2, "relfilenode",
+                                                  OIDOID, -1, 0);
+               TupleDescInitEntry(tupledesc, (AttrNumber) 3, "reltablespace",
+                                                  OIDOID, -1, 0);
+               TupleDescInitEntry(tupledesc, (AttrNumber) 4, "reldatabase",
+                                                  OIDOID, -1, 0);
+               TupleDescInitEntry(tupledesc, (AttrNumber) 5, "relblocknumber",
+                                                  INT8OID, -1, 0);
+               TupleDescInitEntry(tupledesc, (AttrNumber) 6, "blockfreebytes",
+                                                  INT4OID, -1, 0);
+
+               /* Generate attribute metadata needed later to produce tuples */
+               funcctx->attinmeta = TupleDescGetAttInMetadata(tupledesc);
+
+               /*
+                * Create a function context for cross-call persistence and initialize
+                * the counters.
+                */
+               fctx = (FreeSpacePagesContext *) palloc(sizeof(FreeSpacePagesContext));
+               funcctx->user_fctx = fctx;
+
+               /* Set an upper bound on the calls */
+               funcctx->max_calls = numPages;  
+
+
+               /* Allocate numPages worth of FreeSpacePagesRec records, this is also
+                * an upper bound.
+                */
+               fctx->record = (FreeSpacePagesRec *) palloc(sizeof(FreeSpacePagesRec) * numPages);
+
+               /* allocate the strings for tuple formation */
+               fctx->values[0] = (char *) palloc(3 * sizeof(uint32) + 1);
+               fctx->values[1] = (char *) palloc(3 * sizeof(uint32) + 1);
+               fctx->values[2] = (char *) palloc(3 * sizeof(uint32) + 1);
+               fctx->values[3] = (char *) palloc(3 * sizeof(uint32) + 1);
+               fctx->values[4] = (char *) palloc(3 * sizeof(uint32) + 1);
+               fctx->values[5] = (char *) palloc(3 * sizeof(uint32) + 1);
+
+
+               /* Return to original context when allocating transient memory */
+               MemoryContextSwitchTo(oldcontext);
+
+
+               /*
+                * Lock free space map and scan though all the relations, 
+                * for each relation, gets all its mapped pages.
+                */
+               LWLockAcquire(FreeSpaceLock, LW_EXCLUSIVE);
+
+
+               i = 0;
+
+               for (fsmrel = FreeSpaceMap->usageList; fsmrel; fsmrel = fsmrel->nextUsage) 
+               {
+
+                       if (fsmrel->isIndex)    
+                       {       /* Index relation. */
+                               IndexFSMPageData *page;
+
+                               page = (IndexFSMPageData *)
+                                               (FreeSpaceMap->arena + fsmrel->firstChunk * CHUNKBYTES);
+
+                               for (nPages = 0; nPages < fsmrel->storedPages; nPages++)
+                               {
+
+                                       fctx->record[i].blockid = i;
+                                       fctx->record[i].relfilenode = fsmrel->key.relNode;
+                                       fctx->record[i].reltablespace = fsmrel->key.spcNode;
+                                       fctx->record[i].reldatabase = fsmrel->key.dbNode;
+                                       fctx->record[i].relblocknumber = IndexFSMPageGetPageNum(page);  
+                                       fctx->record[i].blockfreebytes = 0;     /* index.*/
+
+                                       page++;
+                                       i++;
+                               }
+                       }
+                       else
+                       {       /* Heap relation. */
+                               FSMPageData *page;
+
+                               page = (FSMPageData *)
+                                               (FreeSpaceMap->arena + fsmrel->firstChunk * CHUNKBYTES);
+
+                               for (nPages = 0; nPages < fsmrel->storedPages; nPages++)
+                               {
+                                       fctx->record[i].blockid = i;
+                                       fctx->record[i].relfilenode = fsmrel->key.relNode;
+                                       fctx->record[i].reltablespace = fsmrel->key.spcNode;
+                                       fctx->record[i].reldatabase = fsmrel->key.dbNode;
+                                       fctx->record[i].relblocknumber = FSMPageGetPageNum(page);
+                                       fctx->record[i].blockfreebytes = FSMPageGetSpace(page); 
+                                       
+                                       page++;
+                                       i++;
+                               }
+                       
+                       }
+
+               }
+
+               /* Set the real no. of calls as we know it now! */
+               funcctx->max_calls = i;
+
+               /* Release free space map. */
+               LWLockRelease(FreeSpaceLock);
+       }
+
+       funcctx = SRF_PERCALL_SETUP();
+
+       /* Get the saved state */
+       fctx = funcctx->user_fctx;
+
+
+       if (funcctx->call_cntr < funcctx->max_calls)
+       {
+               uint32          i = funcctx->call_cntr;
+
+
+               sprintf(fctx->values[0], "%u", fctx->record[i].blockid);
+               sprintf(fctx->values[1], "%u", fctx->record[i].relfilenode);
+               sprintf(fctx->values[2], "%u", fctx->record[i].reltablespace);
+               sprintf(fctx->values[3], "%u", fctx->record[i].reldatabase);
+               sprintf(fctx->values[4], "%u", fctx->record[i].relblocknumber);
+               sprintf(fctx->values[5], "%u", fctx->record[i].blockfreebytes);
+
+
+
+               /* Build and return the tuple. */
+               tuple = BuildTupleFromCStrings(funcctx->attinmeta, fctx->values);
+               result = HeapTupleGetDatum(tuple);
+
+
+               SRF_RETURN_NEXT(funcctx, result);
+       }
+       else
+               SRF_RETURN_DONE(funcctx);
+
+}
diff --git a/contrib/pg_freespacemap/pg_freespacemap.sql.in b/contrib/pg_freespacemap/pg_freespacemap.sql.in
new file mode 100644 (file)
index 0000000..a185186
--- /dev/null
@@ -0,0 +1,19 @@
+-- Adjust this setting to control where the objects get created.
+BEGIN;
+SET search_path = public;
+
+-- Register the function.
+CREATE OR REPLACE FUNCTION pg_freespacemap()
+RETURNS SETOF RECORD
+AS 'MODULE_PATHNAME', 'pg_freespacemap'
+LANGUAGE 'C';
+
+-- Create a view for convenient access.
+CREATE VIEW pg_freespacemap AS
+       SELECT P.* FROM pg_freespacemap() AS P
+       (blockid int4, relfilenode oid, reltablespace oid, reldatabase oid, relblocknumber int8, blockfreebytes int4);
+-- Don't want these to be available at public.
+REVOKE ALL ON FUNCTION pg_freespacemap() FROM PUBLIC;
+REVOKE ALL ON pg_freespacemap FROM PUBLIC;
+COMMIT;
index 8810f1fa33cbf3eda993f6a8aa9feaf1c1ddc641..6987533a08fe8098b73e9ff036f9716f5030a8b6 100644 (file)
@@ -8,7 +8,7 @@
  * Portions Copyright (c) 1994, Regents of the University of California
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/storage/freespace/freespace.c,v 1.50 2005/10/29 00:31:51 petere Exp $
+ *       $PostgreSQL: pgsql/src/backend/storage/freespace/freespace.c,v 1.51 2006/02/12 03:55:53 momjian Exp $
  *
  *
  * NOTES:
 #include "storage/shmem.h"
 
 
-/* Initial value for average-request moving average */
-#define INITIAL_AVERAGE ((Size) (BLCKSZ / 32))
-
-/*
- * Number of pages and bytes per allocation chunk.     Indexes can squeeze 50%
- * more pages into the same space because they don't need to remember how much
- * free space on each page.  The nominal number of pages, CHUNKPAGES, is for
- * regular rels, and INDEXCHUNKPAGES is for indexes.  CHUNKPAGES should be
- * even so that no space is wasted in the index case.
- */
-#define CHUNKPAGES     16
-#define CHUNKBYTES     (CHUNKPAGES * sizeof(FSMPageData))
-#define INDEXCHUNKPAGES ((int) (CHUNKBYTES / sizeof(IndexFSMPageData)))
-
-
-/*
- * Typedefs and macros for items in the page-storage arena.  We use the
- * existing ItemPointer and BlockId data structures, which are designed
- * to pack well (they should be 6 and 4 bytes apiece regardless of machine
- * alignment issues).  Unfortunately we can't use the ItemPointer access
- * macros, because they include Asserts insisting that ip_posid != 0.
- */
-typedef ItemPointerData FSMPageData;
-typedef BlockIdData IndexFSMPageData;
-
-#define FSMPageGetPageNum(ptr) \
-       BlockIdGetBlockNumber(&(ptr)->ip_blkid)
-#define FSMPageGetSpace(ptr)   \
-       ((Size) (ptr)->ip_posid)
-#define FSMPageSetPageNum(ptr, pg)     \
-       BlockIdSet(&(ptr)->ip_blkid, pg)
-#define FSMPageSetSpace(ptr, sz)       \
-       ((ptr)->ip_posid = (OffsetNumber) (sz))
-#define IndexFSMPageGetPageNum(ptr) \
-       BlockIdGetBlockNumber(ptr)
-#define IndexFSMPageSetPageNum(ptr, pg) \
-       BlockIdSet(ptr, pg)
-
 /*----------
  * During database shutdown, we store the contents of FSM into a disk file,
  * which is re-read during startup.  This way we don't have a startup
@@ -156,63 +118,6 @@ typedef struct FsmCacheRelHeader
        int32           storedPages;    /* # of pages stored in arena */
 } FsmCacheRelHeader;
 
-
-/*
- * Shared free-space-map objects
- *
- * The per-relation objects are indexed by a hash table, and are also members
- * of two linked lists: one ordered by recency of usage (most recent first),
- * and the other ordered by physical location of the associated storage in
- * the page-info arena.
- *
- * Each relation owns one or more chunks of per-page storage in the "arena".
- * The chunks for each relation are always consecutive, so that it can treat
- * its page storage as a simple array. We further insist that its page data
- * be ordered by block number, so that binary search is possible.
- *
- * Note: we handle pointers to these items as pointers, not as SHMEM_OFFSETs.
- * This assumes that all processes accessing the map will have the shared
- * memory segment mapped at the same place in their address space.
- */
-typedef struct FSMHeader FSMHeader;
-typedef struct FSMRelation FSMRelation;
-
-/* Header for whole map */
-struct FSMHeader
-{
-       FSMRelation *usageList;         /* FSMRelations in usage-recency order */
-       FSMRelation *usageListTail; /* tail of usage-recency list */
-       FSMRelation *firstRel;          /* FSMRelations in arena storage order */
-       FSMRelation *lastRel;           /* tail of storage-order list */
-       int                     numRels;                /* number of FSMRelations now in use */
-       double          sumRequests;    /* sum of requested chunks over all rels */
-       char       *arena;                      /* arena for page-info storage */
-       int                     totalChunks;    /* total size of arena, in chunks */
-       int                     usedChunks;             /* # of chunks assigned */
-       /* NB: there are totalChunks - usedChunks free chunks at end of arena */
-};
-
-/*
- * Per-relation struct --- this is an entry in the shared hash table.
- * The hash key is the RelFileNode value (hence, we look at the physical
- * relation ID, not the logical ID, which is appropriate).
- */
-struct FSMRelation
-{
-       RelFileNode key;                        /* hash key (must be first) */
-       FSMRelation *nextUsage;         /* next rel in usage-recency order */
-       FSMRelation *priorUsage;        /* prior rel in usage-recency order */
-       FSMRelation *nextPhysical;      /* next rel in arena-storage order */
-       FSMRelation *priorPhysical; /* prior rel in arena-storage order */
-       bool            isIndex;                /* if true, we store only page numbers */
-       Size            avgRequest;             /* moving average of space requests */
-       int                     lastPageCount;  /* pages passed to RecordRelationFreeSpace */
-       int                     firstChunk;             /* chunk # of my first chunk in arena */
-       int                     storedPages;    /* # of pages stored in arena */
-       int                     nextPage;               /* index (from 0) to start next search at */
-};
-
-
 int                    MaxFSMRelations;        /* these are set by guc.c */
 int                    MaxFSMPages;
 
@@ -1838,6 +1743,17 @@ fsm_current_allocation(FSMRelation *fsmrel)
 }
 
 
+/*
+ * Return the FreeSpaceMap structure for examination.
+ */
+FSMHeader *
+GetFreeSpaceMap(void)
+{
+
+       return FreeSpaceMap;
+}
+
+
 #ifdef FREESPACE_DEBUG
 /*
  * Dump contents of freespace map for debugging.
index 1ce7b7f08ae08918001cd1e7778b4596c27e0204..9925793b4fa95dd532c45e679d57e741a323f7a6 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/storage/freespace.h,v 1.18 2005/08/20 23:26:33 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/storage/freespace.h,v 1.19 2006/02/12 03:55:53 momjian Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -16,6 +16,7 @@
 
 #include "storage/block.h"
 #include "storage/relfilenode.h"
+#include "storage/itemptr.h"
 
 
 /*
@@ -28,6 +29,101 @@ typedef struct PageFreeSpaceInfo
 } PageFreeSpaceInfo;
 
 
+/* Initial value for average-request moving average */
+#define INITIAL_AVERAGE ((Size) (BLCKSZ / 32))
+
+/*
+ * Number of pages and bytes per allocation chunk.     Indexes can squeeze 50%
+ * more pages into the same space because they don't need to remember how much
+ * free space on each page.  The nominal number of pages, CHUNKPAGES, is for
+ * regular rels, and INDEXCHUNKPAGES is for indexes.  CHUNKPAGES should be
+ * even so that no space is wasted in the index case.
+ */
+#define CHUNKPAGES     16
+#define CHUNKBYTES     (CHUNKPAGES * sizeof(FSMPageData))
+#define INDEXCHUNKPAGES ((int) (CHUNKBYTES / sizeof(IndexFSMPageData)))
+
+
+/*
+ * Typedefs and macros for items in the page-storage arena.  We use the
+ * existing ItemPointer and BlockId data structures, which are designed
+ * to pack well (they should be 6 and 4 bytes apiece regardless of machine
+ * alignment issues).  Unfortunately we can't use the ItemPointer access
+ * macros, because they include Asserts insisting that ip_posid != 0.
+ */
+typedef ItemPointerData FSMPageData;
+typedef BlockIdData IndexFSMPageData;
+
+#define FSMPageGetPageNum(ptr) \
+       BlockIdGetBlockNumber(&(ptr)->ip_blkid)
+#define FSMPageGetSpace(ptr)   \
+       ((Size) (ptr)->ip_posid)
+#define FSMPageSetPageNum(ptr, pg)     \
+       BlockIdSet(&(ptr)->ip_blkid, pg)
+#define FSMPageSetSpace(ptr, sz)       \
+       ((ptr)->ip_posid = (OffsetNumber) (sz))
+#define IndexFSMPageGetPageNum(ptr) \
+       BlockIdGetBlockNumber(ptr)
+#define IndexFSMPageSetPageNum(ptr, pg) \
+       BlockIdSet(ptr, pg)
+
+/*
+ * Shared free-space-map objects
+ *
+ * The per-relation objects are indexed by a hash table, and are also members
+ * of two linked lists: one ordered by recency of usage (most recent first),
+ * and the other ordered by physical location of the associated storage in
+ * the page-info arena.
+ *
+ * Each relation owns one or more chunks of per-page storage in the "arena".
+ * The chunks for each relation are always consecutive, so that it can treat
+ * its page storage as a simple array. We further insist that its page data
+ * be ordered by block number, so that binary search is possible.
+ *
+ * Note: we handle pointers to these items as pointers, not as SHMEM_OFFSETs.
+ * This assumes that all processes accessing the map will have the shared
+ * memory segment mapped at the same place in their address space.
+ */
+typedef struct FSMHeader FSMHeader;
+typedef struct FSMRelation FSMRelation;
+
+/* Header for whole map */
+struct FSMHeader
+{
+       FSMRelation *usageList;         /* FSMRelations in usage-recency order */
+       FSMRelation *usageListTail; /* tail of usage-recency list */
+       FSMRelation *firstRel;          /* FSMRelations in arena storage order */
+       FSMRelation *lastRel;           /* tail of storage-order list */
+       int                     numRels;                /* number of FSMRelations now in use */
+       double          sumRequests;    /* sum of requested chunks over all rels */
+       char       *arena;                      /* arena for page-info storage */
+       int                     totalChunks;    /* total size of arena, in chunks */
+       int                     usedChunks;             /* # of chunks assigned */
+       /* NB: there are totalChunks - usedChunks free chunks at end of arena */
+};
+
+/*
+ * Per-relation struct --- this is an entry in the shared hash table.
+ * The hash key is the RelFileNode value (hence, we look at the physical
+ * relation ID, not the logical ID, which is appropriate).
+ */
+struct FSMRelation
+{
+       RelFileNode key;                        /* hash key (must be first) */
+       FSMRelation *nextUsage;         /* next rel in usage-recency order */
+       FSMRelation *priorUsage;        /* prior rel in usage-recency order */
+       FSMRelation *nextPhysical;      /* next rel in arena-storage order */
+       FSMRelation *priorPhysical; /* prior rel in arena-storage order */
+       bool            isIndex;                /* if true, we store only page numbers */
+       Size            avgRequest;             /* moving average of space requests */
+       int                     lastPageCount;  /* pages passed to RecordRelationFreeSpace */
+       int                     firstChunk;             /* chunk # of my first chunk in arena */
+       int                     storedPages;    /* # of pages stored in arena */
+       int                     nextPage;               /* index (from 0) to start next search at */
+};
+
+
+
 /* GUC variables */
 extern int     MaxFSMRelations;
 extern int     MaxFSMPages;
@@ -62,6 +158,7 @@ extern void PrintFreeSpaceMapStatistics(int elevel);
 
 extern void DumpFreeSpaceMap(int code, Datum arg);
 extern void LoadFreeSpaceMap(void);
+extern FSMHeader *GetFreeSpaceMap(void);
 
 #ifdef FREESPACE_DEBUG
 extern void DumpFreeSpace(void);