mirror of https://github.com/postgres/postgres
pgstattuple: Add pgstathashindex.
Since pgstattuple v1.5 hasn't been released yet, no need for a new extension version. The new function exposes statistics about hash indexes similar to what other pgstatindex functions return for other index types. Ashutosh Sharma, reviewed by Kuntal Ghosh. Substantial further revisions by me.
This commit is contained in:
parent
39b8cc991f
commit
e759854a09
|
@ -130,3 +130,11 @@ select * from pgstatginindex('test_ginidx');
|
|||
2 | 0 | 0
|
||||
(1 row)
|
||||
|
||||
create index test_hashidx on test using hash (b);
|
||||
WARNING: hash indexes are not WAL-logged and their use is discouraged
|
||||
select * from pgstathashindex('test_hashidx');
|
||||
version | bucket_pages | overflow_pages | bitmap_pages | zero_pages | live_items | dead_items | free_percent
|
||||
---------+--------------+----------------+--------------+------------+------------+------------+--------------
|
||||
2 | 4 | 0 | 1 | 0 | 0 | 0 | 100
|
||||
(1 row)
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
|
||||
#include "access/gin_private.h"
|
||||
#include "access/heapam.h"
|
||||
#include "access/hash.h"
|
||||
#include "access/htup_details.h"
|
||||
#include "access/nbtree.h"
|
||||
#include "catalog/namespace.h"
|
||||
|
@ -36,6 +37,7 @@
|
|||
#include "funcapi.h"
|
||||
#include "miscadmin.h"
|
||||
#include "storage/bufmgr.h"
|
||||
#include "storage/lmgr.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/rel.h"
|
||||
#include "utils/varlena.h"
|
||||
|
@ -54,6 +56,7 @@ PG_FUNCTION_INFO_V1(pgstatindexbyid);
|
|||
PG_FUNCTION_INFO_V1(pg_relpages);
|
||||
PG_FUNCTION_INFO_V1(pg_relpagesbyid);
|
||||
PG_FUNCTION_INFO_V1(pgstatginindex);
|
||||
PG_FUNCTION_INFO_V1(pgstathashindex);
|
||||
|
||||
PG_FUNCTION_INFO_V1(pgstatindex_v1_5);
|
||||
PG_FUNCTION_INFO_V1(pgstatindexbyid_v1_5);
|
||||
|
@ -66,6 +69,7 @@ Datum pgstatginindex_internal(Oid relid, FunctionCallInfo fcinfo);
|
|||
#define IS_INDEX(r) ((r)->rd_rel->relkind == RELKIND_INDEX)
|
||||
#define IS_BTREE(r) ((r)->rd_rel->relam == BTREE_AM_OID)
|
||||
#define IS_GIN(r) ((r)->rd_rel->relam == GIN_AM_OID)
|
||||
#define IS_HASH(r) ((r)->rd_rel->relam == HASH_AM_OID)
|
||||
|
||||
/* ------------------------------------------------
|
||||
* A structure for a whole btree index statistics
|
||||
|
@ -102,7 +106,29 @@ typedef struct GinIndexStat
|
|||
int64 pending_tuples;
|
||||
} GinIndexStat;
|
||||
|
||||
/* ------------------------------------------------
|
||||
* A structure for a whole HASH index statistics
|
||||
* used by pgstathashindex().
|
||||
* ------------------------------------------------
|
||||
*/
|
||||
typedef struct HashIndexStat
|
||||
{
|
||||
int32 version;
|
||||
int32 space_per_page;
|
||||
|
||||
BlockNumber bucket_pages;
|
||||
BlockNumber overflow_pages;
|
||||
BlockNumber bitmap_pages;
|
||||
BlockNumber zero_pages;
|
||||
|
||||
int64 live_items;
|
||||
int64 dead_items;
|
||||
uint64 free_space;
|
||||
} HashIndexStat;
|
||||
|
||||
static Datum pgstatindex_impl(Relation rel, FunctionCallInfo fcinfo);
|
||||
static void GetHashPageStats(Page page, HashIndexStat *stats);
|
||||
|
||||
|
||||
/* ------------------------------------------------------
|
||||
* pgstatindex()
|
||||
|
@ -528,3 +554,172 @@ pgstatginindex_internal(Oid relid, FunctionCallInfo fcinfo)
|
|||
|
||||
return (result);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------
|
||||
* pgstathashindex()
|
||||
*
|
||||
* Usage: SELECT * FROM pgstathashindex('hashindex');
|
||||
* ------------------------------------------------------
|
||||
*/
|
||||
Datum
|
||||
pgstathashindex(PG_FUNCTION_ARGS)
|
||||
{
|
||||
Oid relid = PG_GETARG_OID(0);
|
||||
BlockNumber nblocks;
|
||||
BlockNumber blkno;
|
||||
Relation rel;
|
||||
HashIndexStat stats;
|
||||
BufferAccessStrategy bstrategy;
|
||||
HeapTuple tuple;
|
||||
TupleDesc tupleDesc;
|
||||
Datum values[8];
|
||||
bool nulls[8];
|
||||
Buffer metabuf;
|
||||
HashMetaPage metap;
|
||||
float8 free_percent;
|
||||
uint64 total_space;
|
||||
|
||||
rel = index_open(relid, AccessShareLock);
|
||||
|
||||
if (!IS_HASH(rel))
|
||||
elog(ERROR, "relation \"%s\" is not a HASH index",
|
||||
RelationGetRelationName(rel));
|
||||
|
||||
/*
|
||||
* Reject attempts to read non-local temporary relations; we would be
|
||||
* likely to get wrong data since we have no visibility into the owning
|
||||
* session's local buffers.
|
||||
*/
|
||||
if (RELATION_IS_OTHER_TEMP(rel))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("cannot access temporary indexes of other sessions")));
|
||||
|
||||
/* Get the information we need from the metapage. */
|
||||
memset(&stats, 0, sizeof(stats));
|
||||
metabuf = _hash_getbuf(rel, HASH_METAPAGE, HASH_READ, LH_META_PAGE);
|
||||
metap = HashPageGetMeta(BufferGetPage(metabuf));
|
||||
stats.version = metap->hashm_version;
|
||||
stats.space_per_page = metap->hashm_bsize;
|
||||
_hash_relbuf(rel, metabuf);
|
||||
|
||||
/* Get the current relation length */
|
||||
nblocks = RelationGetNumberOfBlocks(rel);
|
||||
|
||||
/* prepare access strategy for this index */
|
||||
bstrategy = GetAccessStrategy(BAS_BULKREAD);
|
||||
|
||||
/* Start from blkno 1 as 0th block is metapage */
|
||||
for (blkno = 1; blkno < nblocks; blkno++)
|
||||
{
|
||||
Buffer buf;
|
||||
Page page;
|
||||
HashPageOpaque opaque;
|
||||
|
||||
CHECK_FOR_INTERRUPTS();
|
||||
|
||||
buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL,
|
||||
bstrategy);
|
||||
LockBuffer(buf, BUFFER_LOCK_SHARE);
|
||||
page = (Page) BufferGetPage(buf);
|
||||
|
||||
if (PageIsNew(page))
|
||||
stats.zero_pages++;
|
||||
else if (PageGetSpecialSize(page) !=
|
||||
MAXALIGN(sizeof(HashPageOpaqueData)))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INDEX_CORRUPTED),
|
||||
errmsg("index \"%s\" contains corrupted page at block %u",
|
||||
RelationGetRelationName(rel),
|
||||
BufferGetBlockNumber(buf))));
|
||||
else
|
||||
{
|
||||
opaque = (HashPageOpaque) PageGetSpecialPointer(page);
|
||||
if (opaque->hasho_flag & LH_BUCKET_PAGE)
|
||||
{
|
||||
stats.bucket_pages++;
|
||||
GetHashPageStats(page, &stats);
|
||||
}
|
||||
else if (opaque->hasho_flag & LH_OVERFLOW_PAGE)
|
||||
{
|
||||
stats.overflow_pages++;
|
||||
GetHashPageStats(page, &stats);
|
||||
}
|
||||
else if (opaque->hasho_flag & LH_BITMAP_PAGE)
|
||||
stats.bitmap_pages++;
|
||||
else
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INDEX_CORRUPTED),
|
||||
errmsg("unexpected page type 0x%04X in HASH index \"%s\" block %u",
|
||||
opaque->hasho_flag, RelationGetRelationName(rel),
|
||||
BufferGetBlockNumber(buf))));
|
||||
}
|
||||
UnlockReleaseBuffer(buf);
|
||||
}
|
||||
|
||||
/* Done accessing the index */
|
||||
index_close(rel, AccessShareLock);
|
||||
|
||||
/* Count zero pages as free space. */
|
||||
stats.free_space += stats.zero_pages * stats.space_per_page;
|
||||
|
||||
/*
|
||||
* Total space available for tuples excludes the metapage and the bitmap
|
||||
* pages.
|
||||
*/
|
||||
total_space = (nblocks - (stats.bitmap_pages + 1)) * stats.space_per_page;
|
||||
|
||||
if (total_space == 0)
|
||||
free_percent = 0.0;
|
||||
else
|
||||
free_percent = 100.0 * stats.free_space / total_space;
|
||||
|
||||
/*
|
||||
* Build a tuple descriptor for our result type
|
||||
*/
|
||||
if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
|
||||
elog(ERROR, "return type must be a row type");
|
||||
|
||||
tupleDesc = BlessTupleDesc(tupleDesc);
|
||||
|
||||
/*
|
||||
* Build and return the tuple
|
||||
*/
|
||||
MemSet(nulls, 0, sizeof(nulls));
|
||||
values[0] = Int32GetDatum(stats.version);
|
||||
values[1] = Int64GetDatum((int64) stats.bucket_pages);
|
||||
values[2] = Int64GetDatum((int64) stats.overflow_pages);
|
||||
values[3] = Int64GetDatum((int64) stats.bitmap_pages);
|
||||
values[4] = Int64GetDatum((int64) stats.zero_pages);
|
||||
values[5] = Int64GetDatum(stats.live_items);
|
||||
values[6] = Int64GetDatum(stats.dead_items);
|
||||
values[7] = Float8GetDatum(free_percent);
|
||||
tuple = heap_form_tuple(tupleDesc, values, nulls);
|
||||
|
||||
PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
|
||||
}
|
||||
|
||||
/* -------------------------------------------------
|
||||
* GetHashPageStatis()
|
||||
*
|
||||
* Collect statistics of single hash page
|
||||
* -------------------------------------------------
|
||||
*/
|
||||
static void
|
||||
GetHashPageStats(Page page, HashIndexStat *stats)
|
||||
{
|
||||
OffsetNumber maxoff = PageGetMaxOffsetNumber(page);
|
||||
int off;
|
||||
|
||||
/* count live and dead tuples, and free space */
|
||||
for (off = FirstOffsetNumber; off <= maxoff; off++)
|
||||
{
|
||||
ItemId id = PageGetItemId(page, off);
|
||||
|
||||
if (!ItemIdIsDead(id))
|
||||
stats->live_items++;
|
||||
else
|
||||
stats->dead_items++;
|
||||
}
|
||||
stats->free_space += PageGetExactFreeSpace(page);
|
||||
}
|
||||
|
|
|
@ -109,3 +109,19 @@ AS 'MODULE_PATHNAME', 'pgstattuple_approx_v1_5'
|
|||
LANGUAGE C STRICT PARALLEL SAFE;
|
||||
|
||||
REVOKE EXECUTE ON FUNCTION pgstattuple_approx(regclass) FROM PUBLIC;
|
||||
|
||||
/* New stuff in 1.5 begins here */
|
||||
|
||||
CREATE OR REPLACE FUNCTION pgstathashindex(IN relname regclass,
|
||||
OUT version INTEGER,
|
||||
OUT bucket_pages BIGINT,
|
||||
OUT overflow_pages BIGINT,
|
||||
OUT bitmap_pages BIGINT,
|
||||
OUT zero_pages BIGINT,
|
||||
OUT live_items BIGINT,
|
||||
OUT dead_items BIGINT,
|
||||
OUT free_percent FLOAT8)
|
||||
AS 'MODULE_PATHNAME', 'pgstathashindex'
|
||||
LANGUAGE C STRICT PARALLEL SAFE;
|
||||
|
||||
REVOKE EXECUTE ON FUNCTION pgstathashindex(regclass) FROM PUBLIC;
|
||||
|
|
|
@ -47,3 +47,7 @@ select pg_relpages(relname) from pg_class where relname = 'test_pkey';
|
|||
create index test_ginidx on test using gin (b);
|
||||
|
||||
select * from pgstatginindex('test_ginidx');
|
||||
|
||||
create index test_hashidx on test using hash (b);
|
||||
|
||||
select * from pgstathashindex('test_hashidx');
|
||||
|
|
|
@ -352,6 +352,101 @@ pending_tuples | 0
|
|||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term>
|
||||
<indexterm>
|
||||
<primary>pgstathashindex</primary>
|
||||
</indexterm>
|
||||
<function>pgstathashindex(regclass) returns record</>
|
||||
</term>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
<function>pgstathashindex</function> returns a record showing information
|
||||
about a HASH index. For example:
|
||||
<programlisting>
|
||||
test=> select * from pgstathashindex('con_hash_index');
|
||||
-[ RECORD 1 ]--+-----------------
|
||||
version | 2
|
||||
bucket_pages | 33081
|
||||
overflow_pages | 0
|
||||
bitmap_pages | 1
|
||||
zero_pages | 32455
|
||||
live_items | 10204006
|
||||
dead_items | 0
|
||||
free_percent | 61.8005949100872
|
||||
</programlisting>
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The output columns are:
|
||||
|
||||
<informaltable>
|
||||
<tgroup cols="3">
|
||||
<thead>
|
||||
<row>
|
||||
<entry>Column</entry>
|
||||
<entry>Type</entry>
|
||||
<entry>Description</entry>
|
||||
</row>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<row>
|
||||
<entry><structfield>version</structfield></entry>
|
||||
<entry><type>integer</type></entry>
|
||||
<entry>HASH version number</entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry><structfield>bucket_pages</structfield></entry>
|
||||
<entry><type>bigint</type></entry>
|
||||
<entry>Number of bucket pages</entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry><structfield>overflow_pages</structfield></entry>
|
||||
<entry><type>bigint</type></entry>
|
||||
<entry>Number of overflow pages</entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry><structfield>bitmap_pages</structfield></entry>
|
||||
<entry><type>bigint</type></entry>
|
||||
<entry>Number of bitmap pages</entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry><structfield>zero_pages</structfield></entry>
|
||||
<entry><type>bigint</type></entry>
|
||||
<entry>Number of new or zero pages</entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry><structfield>live_items</structfield></entry>
|
||||
<entry><type>bigint</type></entry>
|
||||
<entry>Number of live tuples</entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry><structfield>dead_tuples</structfield></entry>
|
||||
<entry><type>bigint</type></entry>
|
||||
<entry>Number of dead tuples</entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry><structfield>free_percent</structfield></entry>
|
||||
<entry><type>float</type></entry>
|
||||
<entry>Percentage of free space</entry>
|
||||
</row>
|
||||
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</informaltable>
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term>
|
||||
<indexterm>
|
||||
|
|
Loading…
Reference in New Issue