]> granicus.if.org Git - postgresql/commitdiff
pgstattuple: Add pgstathashindex.
authorRobert Haas <rhaas@postgresql.org>
Fri, 3 Feb 2017 19:35:25 +0000 (14:35 -0500)
committerRobert Haas <rhaas@postgresql.org>
Fri, 3 Feb 2017 19:37:16 +0000 (14:37 -0500)
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.

contrib/pgstattuple/expected/pgstattuple.out
contrib/pgstattuple/pgstatindex.c
contrib/pgstattuple/pgstattuple--1.4--1.5.sql
contrib/pgstattuple/sql/pgstattuple.sql
doc/src/sgml/pgstattuple.sgml

index e920234488e1159bf8e9e442e8e77aab6d94b054..169d1932b20738c0f4f2792b9bec6907b90e45b7 100644 (file)
@@ -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)
+
index b40669250addab279dce35dae442aec885ec0c30..17a53e3bb7d790dee621f5c3c3a1566b9de76b5b 100644 (file)
@@ -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);
+}
index 65d7f19c2a85ac68fa1196c7cd1b44c9607e766d..84e112e1c2f0bebb276bc61e22e8e659c28d4cf5 100644 (file)
@@ -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;
index d22c9f1c46dc1432339feda59c4248378a4fc22e..81fd5d693b408dad01b1f4640705456dbf2661c4 100644 (file)
@@ -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');
index d2fa524d6ec7edf7b8a8b394218ddc7f952aa126..62b1a6f4794c9255ba5884d75db45cbe068045c7 100644 (file)
@@ -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=&gt; 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>