diff --git a/contrib/pgstattuple/pgstattuple--1.1.sql b/contrib/pgstattuple/pgstattuple--1.1.sql index d94c20f..08507c1 100644 *** a/contrib/pgstattuple/pgstattuple--1.1.sql --- b/contrib/pgstattuple/pgstattuple--1.1.sql *************** LANGUAGE C STRICT; *** 51,57 **** -- -- relation_free_space() -- ! CREATE FUNCTION relation_free_space(IN relname text) RETURNS real AS 'MODULE_PATHNAME', 'relation_free_space' LANGUAGE C STRICT; --- 51,57 ---- -- -- relation_free_space() -- ! CREATE FUNCTION relation_free_space(IN relname text, IN stats_target int DEFAULT 100) RETURNS real AS 'MODULE_PATHNAME', 'relation_free_space' LANGUAGE C STRICT; diff --git a/contrib/pgstattuple/pgstattuple.c b/contrib/pgstattuple/pgstattuple.c index 1001035..8649b6a 100644 *** a/contrib/pgstattuple/pgstattuple.c --- b/contrib/pgstattuple/pgstattuple.c *************** static Datum pgstat_index(Relation rel, *** 86,93 **** static void pgstat_index_page(pgstattuple_type *stat, Page page, OffsetNumber minoff, OffsetNumber maxoff, bool enable_counters); ! static float4 GetHeapRelationFreeSpace(Relation rel); ! static float4 GetIndexRelationFreeSpace(Relation rel); /* * Buffer access strategy for reading relations, it's simpler to keep it --- 86,94 ---- static void pgstat_index_page(pgstattuple_type *stat, Page page, OffsetNumber minoff, OffsetNumber maxoff, bool enable_counters); ! static float4 GetHeapRelationFreeSpace(Relation rel, int32 stats_target); ! static float4 GetIndexRelationFreeSpace(Relation rel, int32 stats_target); ! static BlockNumber get_next_block(BlockNumber blkno, BlockNumber nblocks, int blocks_read, int32 stats_target); /* * Buffer access strategy for reading relations, it's simpler to keep it *************** Datum *** 621,630 **** --- 622,637 ---- relation_free_space(PG_FUNCTION_ARGS) { text *relname = PG_GETARG_TEXT_P(0); + int32 stats_target = PG_GETARG_INT32(1); RangeVar *relrv; Relation rel; float4 free_space = 0; + if (stats_target < 1 || stats_target > 100) + ereport(ERROR, + (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), + errmsg("parameter stats_target should be between 1 and 100"))); + relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname)); rel = relation_openrv(relrv, AccessShareLock); *************** relation_free_space(PG_FUNCTION_ARGS) *** 651,660 **** { case RELKIND_RELATION: case RELKIND_TOASTVALUE: ! free_space = GetHeapRelationFreeSpace(rel); break; case RELKIND_INDEX: ! free_space = GetIndexRelationFreeSpace(rel); break; default: /* we are not supposed to get here */ --- 658,667 ---- { case RELKIND_RELATION: case RELKIND_TOASTVALUE: ! free_space = GetHeapRelationFreeSpace(rel, stats_target); break; case RELKIND_INDEX: ! free_space = GetIndexRelationFreeSpace(rel, stats_target); break; default: /* we are not supposed to get here */ *************** relation_free_space(PG_FUNCTION_ARGS) *** 675,686 **** * This is a helper function for relation_free_space() */ static float4 ! GetHeapRelationFreeSpace(Relation rel) { Buffer buffer; Page page; BlockNumber blkno = 0; BlockNumber nblocks; Size free_space = 0; double free_percent = 0; --- 682,695 ---- * This is a helper function for relation_free_space() */ static float4 ! GetHeapRelationFreeSpace(Relation rel, int32 stats_target) { Buffer buffer; Page page; BlockNumber blkno = 0; BlockNumber nblocks; + BlockNumber totalBlocksInRelation; + int blocks_read = 0; Size free_space = 0; double free_percent = 0; *************** GetHeapRelationFreeSpace(Relation rel) *** 691,716 **** { /* Get the current relation length */ LockRelationForExtension(rel, ExclusiveLock); ! nblocks = RelationGetNumberOfBlocks(rel); UnlockRelationForExtension(rel, ExclusiveLock); /* Quit if we've scanned the whole relation */ if (blkno >= nblocks) { break; } ! for (; blkno < nblocks; blkno++) { CHECK_FOR_INTERRUPTS(); buffer = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, bstrategy); LockBuffer(buffer, BUFFER_LOCK_SHARE); page = BufferGetPage(buffer); free_space += PageGetHeapFreeSpace(page); UnlockReleaseBuffer(buffer); } } --- 700,733 ---- { /* Get the current relation length */ LockRelationForExtension(rel, ExclusiveLock); ! totalBlocksInRelation = RelationGetNumberOfBlocks(rel); UnlockRelationForExtension(rel, ExclusiveLock); + nblocks = totalBlocksInRelation * stats_target / 100; + /* Quit if we've scanned the whole relation */ if (blkno >= nblocks) { break; } ! for (; blkno < nblocks; ) { CHECK_FOR_INTERRUPTS(); buffer = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, bstrategy); LockBuffer(buffer, BUFFER_LOCK_SHARE); + blocks_read++; page = BufferGetPage(buffer); free_space += PageGetHeapFreeSpace(page); UnlockReleaseBuffer(buffer); + + if (stats_target == 100) + blkno++; + else + blkno = get_next_block(blkno, totalBlocksInRelation, blocks_read, stats_target); } } *************** GetHeapRelationFreeSpace(Relation rel) *** 728,737 **** *------------------------------------------------ */ static float4 ! GetIndexRelationFreeSpace(Relation rel) { - BlockNumber nblocks; BlockNumber blkno; pgstattuple_type stat = {0}; double free_percent = 0; --- 745,756 ---- *------------------------------------------------ */ static float4 ! GetIndexRelationFreeSpace(Relation rel, int32 stats_target) { BlockNumber blkno; + BlockNumber nblocks; + BlockNumber totalBlocksInRelation; + int blocks_read = 0; pgstattuple_type stat = {0}; double free_percent = 0; *************** GetIndexRelationFreeSpace(Relation rel) *** 764,779 **** { /* Get the current relation length */ LockRelationForExtension(rel, ExclusiveLock); ! nblocks = RelationGetNumberOfBlocks(rel); UnlockRelationForExtension(rel, ExclusiveLock); /* Quit if we've scanned the whole relation */ if (blkno >= nblocks) { break; } ! for (; blkno < nblocks; blkno++) { CHECK_FOR_INTERRUPTS(); --- 783,800 ---- { /* Get the current relation length */ LockRelationForExtension(rel, ExclusiveLock); ! totalBlocksInRelation = RelationGetNumberOfBlocks(rel); UnlockRelationForExtension(rel, ExclusiveLock); + nblocks = totalBlocksInRelation * stats_target / 100; + /* Quit if we've scanned the whole relation */ if (blkno >= nblocks) { break; } ! for (; blkno < nblocks; ) { CHECK_FOR_INTERRUPTS(); *************** GetIndexRelationFreeSpace(Relation rel) *** 798,803 **** --- 819,830 ---- elog(ERROR, "Unknown index type %d", rel->rd_rel->relam); break; } + blocks_read++; + + if (stats_target == 100) + blkno++; + else + get_next_block(blkno, totalBlocksInRelation, blocks_read, stats_target); } } *************** GetIndexRelationFreeSpace(Relation rel) *** 806,808 **** --- 833,870 ---- return free_percent; } + + static BlockNumber + get_next_block(BlockNumber blkno, BlockNumber nblocks, int blocks_read, int32 stats_target) + { + BlockNumber K = nblocks - blkno; /* remaining blocks */ + int k = (nblocks * stats_target / 100) - blocks_read; /* blocks still to sample */ + double p; /* probability to skip block */ + double V; + + if (k > K) + { + /* need all the rest */ + return blkno + 1; + } + + V = ((double) random() + 1) / ((double) MAX_RANDOM_VALUE + 2); + p = 1.0 - (double) k / (double) K; + while (V < p) + { + if (k > K) + { + /* need all the rest */ + return blkno + 1; + } + + /* skip */ + blkno++; + K--; /* keep K == N - t */ + + /* adjust p to be new cutoff point in reduced range */ + p *= 1.0 - (double) k / (double) K; + } + + return blkno + 1; + }