diff --git a/contrib/pgstattuple/Makefile b/contrib/pgstattuple/Makefile index 6d25c87867..fb04915c70 100644 --- a/contrib/pgstattuple/Makefile +++ b/contrib/pgstattuple/Makefile @@ -2,11 +2,11 @@ # # pgstattuple Makefile # -# $PostgreSQL: pgsql/contrib/pgstattuple/Makefile,v 1.5 2006/02/27 12:54:39 petere Exp $ +# $PostgreSQL: pgsql/contrib/pgstattuple/Makefile,v 1.6 2006/09/02 17:05:29 momjian Exp $ # #------------------------------------------------------------------------- -SRCS = pgstattuple.c +SRCS = pgstattuple.c pgstatindex.c MODULE_big = pgstattuple OBJS = $(SRCS:.c=.o) diff --git a/contrib/pgstattuple/README.pgstattuple b/contrib/pgstattuple/README.pgstattuple index eace7c2f98..235de72559 100644 --- a/contrib/pgstattuple/README.pgstattuple +++ b/contrib/pgstattuple/README.pgstattuple @@ -1,36 +1,108 @@ pgstattuple README 2002/08/29 Tatsuo Ishii -1. What is pgstattuple? +1. Functions supported: - pgstattuple returns the relation length, percentage of the "dead" - tuples of a relation and other info. This may help users to determine - whether vacuum is necessary or not. Here is an example session: + pgstattuple + ----------- + pgstattuple() returns the relation length, percentage of the "dead" + tuples of a relation and other info. This may help users to determine + whether vacuum is necessary or not. Here is an example session: -test=# \x -Expanded display is on. -test=# select * from pgstattuple('pg_catalog.pg_proc'); --[ RECORD 1 ]------+------- -table_len | 458752 -tuple_count | 1470 -tuple_len | 438896 -tuple_percent | 95.67 -dead_tuple_count | 11 -dead_tuple_len | 3157 -dead_tuple_percent | 0.69 -free_space | 8932 -free_percent | 1.95 + test=> \x + Expanded display is on. + test=> SELECT * FROM pgstattuple('pg_catalog.pg_proc'); + -[ RECORD 1 ]------+------- + table_len | 458752 + tuple_count | 1470 + tuple_len | 438896 + tuple_percent | 95.67 + dead_tuple_count | 11 + dead_tuple_len | 3157 + dead_tuple_percent | 0.69 + free_space | 8932 + free_percent | 1.95 + Here are explanations for each column: -Here are explanations for each column: + table_len -- physical relation length in bytes + tuple_count -- number of live tuples + tuple_len -- total tuples length in bytes + tuple_percent -- live tuples in % + dead_tuple_len -- total dead tuples length in bytes + dead_tuple_percent -- dead tuples in % + free_space -- free space in bytes + free_percent -- free space in % + + pg_relpages + ----------- + pg_relpages() returns the number of pages in the relation. + + pgstatindex + ----------- + pgstatindex() returns an array showing the information about an index: + + test=> \x + Expanded display is on. + test=> SELECT * FROM pgstatindex('pg_cast_oid_index'); + -[ RECORD 1 ]------+------ + version | 2 + tree_level | 0 + index_size | 8192 + root_block_no | 1 + internal_pages | 0 + leaf_pages | 1 + empty_pages | 0 + deleted_pages | 0 + avg_leaf_density | 50.27 + leaf_fragmentation | 0 + + bt_metap + -------- + bt_metap() returns information about the btree index metapage: + + test=> SELECT * FROM bt_metap('pg_cast_oid_index'); + -[ RECORD 1 ]----- + magic | 340322 + version | 2 + root | 1 + level | 0 + fastroot | 1 + fastlevel | 0 + + bt_page_stats + ------------- + bt_page_stats() shows information about single btree pages: + + test=> SELECT * FROM bt_page_stats('pg_cast_oid_index', 1); + -[ RECORD 1 ]-+----- + blkno | 1 + type | l + live_items | 256 + dead_items | 0 + avg_item_size | 12 + page_size | 8192 + free_size | 4056 + btpo_prev | 0 + btpo_next | 0 + btpo | 0 + btpo_flags | 3 + + bt_page_items + ------------- + bt_page_items() returns information about specific items on btree pages: + + test=> SELECT * FROM bt_page_items('pg_cast_oid_index', 1); + itemoffset | ctid | itemlen | nulls | vars | data + ------------+---------+---------+-------+------+------------- + 1 | (0,1) | 12 | f | f | 23 27 00 00 + 2 | (0,2) | 12 | f | f | 24 27 00 00 + 3 | (0,3) | 12 | f | f | 25 27 00 00 + 4 | (0,4) | 12 | f | f | 26 27 00 00 + 5 | (0,5) | 12 | f | f | 27 27 00 00 + 6 | (0,6) | 12 | f | f | 28 27 00 00 + 7 | (0,7) | 12 | f | f | 29 27 00 00 + 8 | (0,8) | 12 | f | f | 2a 27 00 00 -table_len -- physical relation length in bytes -tuple_count -- number of live tuples -tuple_len -- total tuples length in bytes -tuple_percent -- live tuples in % -dead_tuple_len -- total dead tuples length in bytes -dead_tuple_percent -- dead tuples in % -free_space -- free space in bytes -free_percent -- free space in % 2. Installing pgstattuple @@ -38,33 +110,36 @@ free_percent -- free space in % $ make install $ psql -e -f /usr/local/pgsql/share/contrib/pgstattuple.sql test + 3. Using pgstattuple - pgstattuple may be called as a relation function and is - defined as follows: + pgstattuple may be called as a relation function and is + defined as follows: - CREATE OR REPLACE FUNCTION pgstattuple(text) RETURNS pgstattuple_type + CREATE OR REPLACE FUNCTION pgstattuple(text) RETURNS pgstattuple_type AS 'MODULE_PATHNAME', 'pgstattuple' LANGUAGE C STRICT; - CREATE OR REPLACE FUNCTION pgstattuple(oid) RETURNS pgstattuple_type + CREATE OR REPLACE FUNCTION pgstattuple(oid) RETURNS pgstattuple_type AS 'MODULE_PATHNAME', 'pgstattuplebyid' LANGUAGE C STRICT; - The argument is the relation name (optionally it may be qualified) - or the OID of the relation. Note that pgstattuple only returns - one row. + The argument is the relation name (optionally it may be qualified) + or the OID of the relation. Note that pgstattuple only returns + one row. + 4. Notes - pgstattuple acquires only a read lock on the relation. So concurrent - update may affect the result. + pgstattuple acquires only a read lock on the relation. So concurrent + update may affect the result. + + pgstattuple judges a tuple is "dead" if HeapTupleSatisfiesNow() + returns false. - pgstattuple judges a tuple is "dead" if HeapTupleSatisfiesNow() - returns false. 5. History - 2006/06/28 + 2006/06/28 Extended to work against indexes. diff --git a/contrib/pgstattuple/pgstatindex.c b/contrib/pgstattuple/pgstatindex.c new file mode 100644 index 0000000000..bda69c252d --- /dev/null +++ b/contrib/pgstattuple/pgstatindex.c @@ -0,0 +1,706 @@ +/* + * pgstatindex + * + * Copyright (c) 2006 Satoshi Nagayasu + * + * Permission to use, copy, modify, and distribute this software and + * its documentation for any purpose, without fee, and without a + * written agreement is hereby granted, provided that the above + * copyright notice and this paragraph and the following two + * paragraphs appear in all copies. + * + * IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, + * INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING + * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS + * DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + * THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS + * IS" BASIS, AND THE AUTHOR HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, + * SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + */ + +#include "postgres.h" + +#include "fmgr.h" +#include "funcapi.h" +#include "access/heapam.h" +#include "access/itup.h" +#include "access/nbtree.h" +#include "access/transam.h" +#include "catalog/namespace.h" +#include "catalog/pg_type.h" +#include "utils/builtins.h" +#include "utils/inval.h" + +PG_FUNCTION_INFO_V1(pgstatindex); +PG_FUNCTION_INFO_V1(bt_metap); +PG_FUNCTION_INFO_V1(bt_page_items); +PG_FUNCTION_INFO_V1(bt_page_stats); +PG_FUNCTION_INFO_V1(pg_relpages); + +extern Datum pgstatindex(PG_FUNCTION_ARGS); +extern Datum bt_metap(PG_FUNCTION_ARGS); +extern Datum bt_page_items(PG_FUNCTION_ARGS); +extern Datum bt_page_stats(PG_FUNCTION_ARGS); +extern Datum pg_relpages(PG_FUNCTION_ARGS); + +#define PGSTATINDEX_TYPE "public.pgstatindex_type" +#define PGSTATINDEX_NCOLUMNS 10 + +#define BTMETAP_TYPE "public.bt_metap_type" +#define BTMETAP_NCOLUMNS 6 + +#define BTPAGEITEMS_TYPE "public.bt_page_items_type" +#define BTPAGEITEMS_NCOLUMNS 6 + +#define BTPAGESTATS_TYPE "public.bt_page_stats_type" +#define BTPAGESTATS_NCOLUMNS 11 + + +#define IS_INDEX(r) ((r)->rd_rel->relkind == 'i') +#define IS_BTREE(r) ((r)->rd_rel->relam == BTREE_AM_OID) + +#define CHECK_PAGE_OFFSET_RANGE(page, offset) { \ + if ( !(FirstOffsetNumber<=(offset) && \ + (offset)<=PageGetMaxOffsetNumber(page)) ) \ + elog(ERROR, "Page offset number out of range."); } + +#define CHECK_RELATION_BLOCK_RANGE(rel, blkno) { \ + if ( (blkno)<0 && RelationGetNumberOfBlocks((rel))<=(blkno) ) \ + elog(ERROR, "Block number out of range."); } + +/* ------------------------------------------------ + * structure for single btree page statistics + * ------------------------------------------------ + */ +typedef struct BTPageStat +{ + uint32 blkno; + uint32 live_items; + uint32 dead_items; + uint32 page_size; + uint32 max_avail; + uint32 free_size; + uint32 avg_item_size; + uint32 fragments; + char type; + + /* opaque data */ + BlockNumber btpo_prev; + BlockNumber btpo_next; + union + { + uint32 level; + TransactionId xact; + } btpo; + uint16 btpo_flags; + BTCycleId btpo_cycleid; +} BTPageStat; + +/* ------------------------------------------------ + * A structure for a whole btree index statistics + * used by pgstatindex(). + * ------------------------------------------------ + */ +typedef struct BTIndexStat +{ + uint32 magic; + uint32 version; + BlockNumber root_blkno; + uint32 level; + + BlockNumber fastroot; + uint32 fastlevel; + + uint32 live_items; + uint32 dead_items; + + uint32 root_pages; + uint32 internal_pages; + uint32 leaf_pages; + uint32 empty_pages; + uint32 deleted_pages; + + uint32 page_size; + uint32 avg_item_size; + + uint32 max_avail; + uint32 free_space; + + uint32 fragments; +} BTIndexStat; + +/* ------------------------------------------------- + * GetBTPageStatistics() + * + * Collect statistics of single b-tree leaf page + * ------------------------------------------------- + */ +static bool +GetBTPageStatistics(BlockNumber blkno, Buffer buffer, BTPageStat * stat) +{ + Page page = BufferGetPage(buffer); + PageHeader phdr = (PageHeader) page; + OffsetNumber maxoff = PageGetMaxOffsetNumber(page); + BTPageOpaque opaque = (BTPageOpaque) PageGetSpecialPointer(page); + int item_size = 0; + int off; + + stat->blkno = blkno; + + stat->max_avail = BLCKSZ - (BLCKSZ - phdr->pd_special + SizeOfPageHeaderData); + + stat->dead_items = stat->live_items = 0; + + stat->page_size = PageGetPageSize(page); + + /* page type (flags) */ + if (P_ISDELETED(opaque)) + { + stat->type = 'd'; + return true; + } + else if (P_IGNORE(opaque)) + stat->type = 'e'; + else if (P_ISLEAF(opaque)) + stat->type = 'l'; + else if (P_ISROOT(opaque)) + stat->type = 'r'; + else + stat->type = 'i'; + + /* btpage opaque data */ + stat->btpo_prev = opaque->btpo_prev; + stat->btpo_next = opaque->btpo_next; + if (P_ISDELETED(opaque)) + stat->btpo.xact = opaque->btpo.xact; + else + stat->btpo.level = opaque->btpo.level; + stat->btpo_flags = opaque->btpo_flags; + stat->btpo_cycleid = opaque->btpo_cycleid; + + /*---------------------------------------------- + * If a next leaf is on the previous block, + * it means a fragmentation. + *---------------------------------------------- + */ + stat->fragments = 0; + if (stat->type == 'l') + { + if (opaque->btpo_next != P_NONE && opaque->btpo_next < blkno) + stat->fragments++; + } + + /* count live and dead tuples, and free space */ + for (off = FirstOffsetNumber; off <= maxoff; off++) + { + IndexTuple itup; + + ItemId id = PageGetItemId(page, off); + + itup = (IndexTuple) PageGetItem(page, id); + + item_size += IndexTupleSize(itup); + + if (!ItemIdDeleted(id)) + stat->live_items++; + else + stat->dead_items++; + } + stat->free_size = PageGetFreeSpace(page); + + if ((stat->live_items + stat->dead_items) > 0) + stat->avg_item_size = item_size / (stat->live_items + stat->dead_items); + else + stat->avg_item_size = 0; + + return true; +} + + +/* ------------------------------------------------------ + * pgstatindex() + * + * Usage: SELECT * FROM pgstatindex('t1_pkey'); + * ------------------------------------------------------ + */ +Datum +pgstatindex(PG_FUNCTION_ARGS) +{ + text *relname = PG_GETARG_TEXT_P(0); + Relation rel; + RangeVar *relrv; + Datum result; + uint32 nblocks; + uint32 blkno; + BTIndexStat indexStat; + + relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname)); + rel = relation_openrv(relrv, AccessShareLock); + + if (!IS_INDEX(rel) || !IS_BTREE(rel)) + elog(ERROR, "pgstatindex() can be used only on b-tree index."); + + /*------------------- + * Read a metapage + *------------------- + */ + { + Buffer buffer = ReadBuffer(rel, 0); + Page page = BufferGetPage(buffer); + BTMetaPageData *metad = BTPageGetMeta(page); + + indexStat.magic = metad->btm_magic; + indexStat.version = metad->btm_version; + indexStat.root_blkno = metad->btm_root; + indexStat.level = metad->btm_level; + indexStat.fastroot = metad->btm_fastroot; + indexStat.fastlevel = metad->btm_fastlevel; + + ReleaseBuffer(buffer); + } + + nblocks = RelationGetNumberOfBlocks(rel); + + /* -- init stat -- */ + indexStat.fragments = 0; + + indexStat.root_pages = 0; + indexStat.leaf_pages = 0; + indexStat.internal_pages = 0; + indexStat.empty_pages = 0; + indexStat.deleted_pages = 0; + + indexStat.max_avail = 0; + indexStat.free_space = 0; + + /*----------------------- + * Scan all blocks + *----------------------- + */ + for (blkno = 1; blkno < nblocks; blkno++) + { + Buffer buffer = ReadBuffer(rel, blkno); + BTPageStat stat; + + /* scan one page */ + stat.blkno = blkno; + GetBTPageStatistics(blkno, buffer, &stat); + + /*--------------------- + * page status (type) + *--------------------- + */ + switch (stat.type) + { + case 'd': + indexStat.deleted_pages++; + break; + case 'l': + indexStat.leaf_pages++; + break; + case 'i': + indexStat.internal_pages++; + break; + case 'e': + indexStat.empty_pages++; + break; + case 'r': + indexStat.root_pages++; + break; + default: + elog(ERROR, "unknown page status."); + } + + /* -- leaf fragmentation -- */ + indexStat.fragments += stat.fragments; + + if (stat.type == 'l') + { + indexStat.max_avail += stat.max_avail; + indexStat.free_space += stat.free_size; + } + + ReleaseBuffer(buffer); + } + + relation_close(rel, AccessShareLock); + + /*---------------------------- + * Build a result tuple + *---------------------------- + */ + { + TupleDesc tupleDesc; + int j; + char *values[PGSTATINDEX_NCOLUMNS]; + + HeapTupleData tupleData; + HeapTuple tuple = &tupleData; + + tupleDesc = RelationNameGetTupleDesc(PGSTATINDEX_TYPE); + + j = 0; + values[j] = palloc(32); + snprintf(values[j++], 32, "%d", indexStat.version); + values[j] = palloc(32); + snprintf(values[j++], 32, "%d", indexStat.level); + values[j] = palloc(32); + snprintf(values[j++], 32, "%d", (indexStat.root_pages + + indexStat.leaf_pages + + indexStat.internal_pages + + indexStat.deleted_pages + + indexStat.empty_pages) * BLCKSZ); + values[j] = palloc(32); + snprintf(values[j++], 32, "%d", indexStat.root_blkno); + values[j] = palloc(32); + snprintf(values[j++], 32, "%d", indexStat.internal_pages); + values[j] = palloc(32); + snprintf(values[j++], 32, "%d", indexStat.leaf_pages); + values[j] = palloc(32); + snprintf(values[j++], 32, "%d", indexStat.empty_pages); + values[j] = palloc(32); + snprintf(values[j++], 32, "%d", indexStat.deleted_pages); + values[j] = palloc(32); + snprintf(values[j++], 32, "%.2f", 100.0 - (float) indexStat.free_space / (float) indexStat.max_avail * 100.0); + values[j] = palloc(32); + snprintf(values[j++], 32, "%.2f", (float) indexStat.fragments / (float) indexStat.leaf_pages * 100.0); + + tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc), + values); + + result = TupleGetDatum(TupleDescGetSlot(tupleDesc), tuple); + } + + PG_RETURN_DATUM(result); +} + +/* ----------------------------------------------- + * bt_page() + * + * Usage: SELECT * FROM bt_page('t1_pkey', 0); + * ----------------------------------------------- + */ +Datum +bt_page_stats(PG_FUNCTION_ARGS) +{ + text *relname = PG_GETARG_TEXT_P(0); + uint32 blkno = PG_GETARG_UINT32(1); + Buffer buffer; + + Relation rel; + RangeVar *relrv; + Datum result; + + relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname)); + rel = relation_openrv(relrv, AccessShareLock); + + CHECK_RELATION_BLOCK_RANGE(rel, blkno); + + buffer = ReadBuffer(rel, blkno); + + if (!IS_INDEX(rel) || !IS_BTREE(rel)) + elog(ERROR, "bt_page_stats() can be used only on b-tree index."); + + if (blkno == 0) + elog(ERROR, "Block 0 is a meta page."); + + { + HeapTuple tuple; + TupleDesc tupleDesc; + int j; + char *values[BTPAGESTATS_NCOLUMNS]; + + BTPageStat stat; + + GetBTPageStatistics(blkno, buffer, &stat); + + tupleDesc = RelationNameGetTupleDesc(BTPAGESTATS_TYPE); + + j = 0; + values[j] = palloc(32); + snprintf(values[j++], 32, "%d", stat.blkno); + + values[j] = palloc(32); + snprintf(values[j++], 32, "%c", stat.type); + values[j] = palloc(32); + snprintf(values[j++], 32, "%d", stat.live_items); + values[j] = palloc(32); + snprintf(values[j++], 32, "%d", stat.dead_items); + values[j] = palloc(32); + snprintf(values[j++], 32, "%d", stat.avg_item_size); + values[j] = palloc(32); + snprintf(values[j++], 32, "%d", stat.page_size); + values[j] = palloc(32); + snprintf(values[j++], 32, "%d", stat.free_size); + values[j] = palloc(32); + snprintf(values[j++], 32, "%d", stat.btpo_prev); + values[j] = palloc(32); + snprintf(values[j++], 32, "%d", stat.btpo_next); + + values[j] = palloc(32); + if (stat.type == 'd') + snprintf(values[j++], 32, "%d", stat.btpo.xact); + else + snprintf(values[j++], 32, "%d", stat.btpo.level); + + values[j] = palloc(32); + snprintf(values[j++], 32, "%d", stat.btpo_flags); + + tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc), + values); + + result = TupleGetDatum(TupleDescGetSlot(tupleDesc), tuple); + } + + ReleaseBuffer(buffer); + + relation_close(rel, AccessShareLock); + + PG_RETURN_DATUM(result); +} + +/*------------------------------------------------------- + * bt_page_items() + * + * Get IndexTupleData set in a leaf page + * + * Usage: SELECT * FROM bt_page_items('t1_pkey', 0); + *------------------------------------------------------- + */ +/* --------------------------------------------------- + * data structure for SRF to hold a scan information + * --------------------------------------------------- + */ +struct user_args +{ + TupleDesc tupd; + Relation rel; + Buffer buffer; + Page page; + uint16 offset; +}; + +Datum +bt_page_items(PG_FUNCTION_ARGS) +{ + text *relname = PG_GETARG_TEXT_P(0); + uint32 blkno = PG_GETARG_UINT32(1); + + RangeVar *relrv; + Datum result; + char *values[BTPAGEITEMS_NCOLUMNS]; + BTPageOpaque opaque; + HeapTuple tuple; + ItemId id; + + FuncCallContext *fctx; + MemoryContext mctx; + struct user_args *uargs = NULL; + + if (blkno == 0) + elog(ERROR, "Block 0 is a meta page."); + + if (SRF_IS_FIRSTCALL()) + { + fctx = SRF_FIRSTCALL_INIT(); + mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx); + + uargs = palloc(sizeof(struct user_args)); + + uargs->tupd = RelationNameGetTupleDesc(BTPAGEITEMS_TYPE); + uargs->offset = FirstOffsetNumber; + + relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname)); + uargs->rel = relation_openrv(relrv, AccessShareLock); + + CHECK_RELATION_BLOCK_RANGE(uargs->rel, blkno); + + uargs->buffer = ReadBuffer(uargs->rel, blkno); + + if (!IS_INDEX(uargs->rel) || !IS_BTREE(uargs->rel)) + elog(ERROR, "bt_page_items() can be used only on b-tree index."); + + uargs->page = BufferGetPage(uargs->buffer); + + opaque = (BTPageOpaque) PageGetSpecialPointer(uargs->page); + + if (P_ISDELETED(opaque)) + elog(NOTICE, "bt_page_items(): this page is deleted."); + + fctx->max_calls = PageGetMaxOffsetNumber(uargs->page); + fctx->user_fctx = uargs; + + MemoryContextSwitchTo(mctx); + } + + fctx = SRF_PERCALL_SETUP(); + uargs = fctx->user_fctx; + + if (fctx->call_cntr < fctx->max_calls) + { + IndexTuple itup; + + id = PageGetItemId(uargs->page, uargs->offset); + + if (!ItemIdIsValid(id)) + elog(ERROR, "Invalid ItemId."); + + itup = (IndexTuple) PageGetItem(uargs->page, id); + + { + int j = 0; + + BlockNumber blkno = BlockIdGetBlockNumber(&(itup->t_tid.ip_blkid)); + + values[j] = palloc(32); + snprintf(values[j++], 32, "%d", uargs->offset); + values[j] = palloc(32); + snprintf(values[j++], 32, "(%u,%u)", blkno, itup->t_tid.ip_posid); + values[j] = palloc(32); + snprintf(values[j++], 32, "%d", IndexTupleSize(itup)); + values[j] = palloc(32); + snprintf(values[j++], 32, "%c", IndexTupleHasNulls(itup) ? 't' : 'f'); + values[j] = palloc(32); + snprintf(values[j++], 32, "%c", IndexTupleHasVarwidths(itup) ? 't' : 'f'); + + { + int off; + char *dump; + char *ptr = (char *) itup + IndexInfoFindDataOffset(itup->t_info); + + dump = palloc(IndexTupleSize(itup) * 3); + memset(dump, 0, IndexTupleSize(itup) * 3); + + for (off = 0; + off < IndexTupleSize(itup) - IndexInfoFindDataOffset(itup->t_info); + off++) + { + if (dump[0] == '\0') + sprintf(dump, "%02x", *(ptr + off) & 0xff); + else + { + char buf[4]; + + sprintf(buf, " %02x", *(ptr + off) & 0xff); + strcat(dump, buf); + } + } + values[j] = dump; + } + + tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(uargs->tupd), values); + result = TupleGetDatum(TupleDescGetSlot(uargs->tupd), tuple); + } + + uargs->offset = uargs->offset + 1; + + SRF_RETURN_NEXT(fctx, result); + } + else + { + ReleaseBuffer(uargs->buffer); + relation_close(uargs->rel, AccessShareLock); + + SRF_RETURN_DONE(fctx); + } +} + + +/* ------------------------------------------------ + * bt_metap() + * + * Get a btree meta-page information + * + * Usage: SELECT * FROM bt_metap('t1_pkey') + * ------------------------------------------------ + */ +Datum +bt_metap(PG_FUNCTION_ARGS) +{ + text *relname = PG_GETARG_TEXT_P(0); + Buffer buffer; + + Relation rel; + RangeVar *relrv; + Datum result; + + relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname)); + rel = relation_openrv(relrv, AccessShareLock); + + if (!IS_INDEX(rel) || !IS_BTREE(rel)) + elog(ERROR, "bt_metap() can be used only on b-tree index."); + + buffer = ReadBuffer(rel, 0); + + { + BTMetaPageData *metad; + + TupleDesc tupleDesc; + int j; + char *values[BTMETAP_NCOLUMNS]; + HeapTuple tuple; + + Page page = BufferGetPage(buffer); + + metad = BTPageGetMeta(page); + + tupleDesc = RelationNameGetTupleDesc(BTMETAP_TYPE); + + j = 0; + values[j] = palloc(32); + snprintf(values[j++], 32, "%d", metad->btm_magic); + values[j] = palloc(32); + snprintf(values[j++], 32, "%d", metad->btm_version); + values[j] = palloc(32); + snprintf(values[j++], 32, "%d", metad->btm_root); + values[j] = palloc(32); + snprintf(values[j++], 32, "%d", metad->btm_level); + values[j] = palloc(32); + snprintf(values[j++], 32, "%d", metad->btm_fastroot); + values[j] = palloc(32); + snprintf(values[j++], 32, "%d", metad->btm_fastlevel); + + tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc), + values); + + result = TupleGetDatum(TupleDescGetSlot(tupleDesc), tuple); + } + + ReleaseBuffer(buffer); + + relation_close(rel, AccessShareLock); + + PG_RETURN_DATUM(result); +} + +/* -------------------------------------------------------- + * pg_relpages() + * + * Get a number of pages of the table/index. + * + * Usage: SELECT pg_relpages('t1'); + * SELECT pg_relpages('t1_pkey'); + * -------------------------------------------------------- + */ +Datum +pg_relpages(PG_FUNCTION_ARGS) +{ + text *relname = PG_GETARG_TEXT_P(0); + + Relation rel; + RangeVar *relrv; + int4 relpages; + + relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname)); + rel = relation_openrv(relrv, AccessShareLock); + + relpages = RelationGetNumberOfBlocks(rel); + + relation_close(rel, AccessShareLock); + + PG_RETURN_INT32(relpages); +} diff --git a/contrib/pgstattuple/pgstattuple.sql.in b/contrib/pgstattuple/pgstattuple.sql.in index 440b978682..cb401d94c7 100644 --- a/contrib/pgstattuple/pgstattuple.sql.in +++ b/contrib/pgstattuple/pgstattuple.sql.in @@ -22,3 +22,96 @@ CREATE OR REPLACE FUNCTION pgstattuple(oid) RETURNS pgstattuple_type AS 'MODULE_PATHNAME', 'pgstattuplebyid' LANGUAGE C STRICT; + +-- +-- pgstatindex +-- +DROP TYPE pgstatindex_type CASCADE; +CREATE TYPE pgstatindex_type AS ( + version int4, + tree_level int4, + index_size int4, + root_block_no int4, + internal_pages int4, + leaf_pages int4, + empty_pages int4, + deleted_pages int4, + avg_leaf_density float8, + leaf_fragmentation float8 +); + +CREATE OR REPLACE FUNCTION pgstatindex(text) +RETURNS pgstatindex_type +AS 'MODULE_PATHNAME', 'pgstatindex' +LANGUAGE 'C' STRICT; + +-- +-- bt_metap() +-- +DROP TYPE bt_metap_type CASCADE; +CREATE TYPE bt_metap_type AS ( + magic int4, + version int4, + root int4, + level int4, + fastroot int4, + fastlevel int4 +); + +CREATE OR REPLACE FUNCTION bt_metap(text) +RETURNS bt_metap_type +AS 'MODULE_PATHNAME', 'bt_metap' +LANGUAGE 'C' STRICT; + +-- +-- bt_page_stats() +-- +DROP TYPE bt_page_stats_type CASCADE; +CREATE TYPE bt_page_stats_type AS ( + blkno int4, + type char, + live_items int4, + dead_items int4, + avg_item_size float, + page_size int4, + free_size int4, + btpo_prev int4, + btpo_next int4, + btpo int4, + btpo_flags int4 +); + +DROP FUNCTION bt_page_stats(text, int4); + +CREATE OR REPLACE FUNCTION bt_page_stats(text, int4) +RETURNS bt_page_stats_type +AS 'MODULE_PATHNAME', 'bt_page_stats' +LANGUAGE 'C' STRICT; + +-- +-- bt_page_items() +-- +DROP TYPE bt_page_items_type CASCADE; +CREATE TYPE bt_page_items_type AS ( + itemoffset int4, + ctid tid, + itemlen int4, + nulls bool, + vars bool, + data text +); + +DROP FUNCTION bt_page_items(text, int4); + +CREATE OR REPLACE FUNCTION bt_page_items(text, int4) +RETURNS SETOF bt_page_items_type +AS 'MODULE_PATHNAME', 'bt_page_items' +LANGUAGE 'C' STRICT; + +-- +-- pg_relpages() +-- +CREATE OR REPLACE FUNCTION pg_relpages(text) +RETURNS int +AS 'MODULE_PATHNAME', 'pg_relpages' +LANGUAGE 'C' STRICT;