2929
3030#include "access/gin_private.h"
3131#include "access/heapam.h"
32+ #include "access/hash.h"
3233#include "access/htup_details.h"
3334#include "access/nbtree.h"
3435#include "catalog/namespace.h"
3536#include "catalog/pg_am.h"
3637#include "funcapi.h"
3738#include "miscadmin.h"
3839#include "storage/bufmgr.h"
40+ #include "storage/lmgr.h"
3941#include "utils/builtins.h"
4042#include "utils/rel.h"
4143#include "utils/varlena.h"
@@ -54,6 +56,7 @@ PG_FUNCTION_INFO_V1(pgstatindexbyid);
5456PG_FUNCTION_INFO_V1 (pg_relpages );
5557PG_FUNCTION_INFO_V1 (pg_relpagesbyid );
5658PG_FUNCTION_INFO_V1 (pgstatginindex );
59+ PG_FUNCTION_INFO_V1 (pgstathashindex );
5760
5861PG_FUNCTION_INFO_V1 (pgstatindex_v1_5 );
5962PG_FUNCTION_INFO_V1 (pgstatindexbyid_v1_5 );
@@ -66,6 +69,7 @@ Datum pgstatginindex_internal(Oid relid, FunctionCallInfo fcinfo);
6669#define IS_INDEX (r ) ((r)->rd_rel->relkind == RELKIND_INDEX)
6770#define IS_BTREE (r ) ((r)->rd_rel->relam == BTREE_AM_OID)
6871#define IS_GIN (r ) ((r)->rd_rel->relam == GIN_AM_OID)
72+ #define IS_HASH (r ) ((r)->rd_rel->relam == HASH_AM_OID)
6973
7074/* ------------------------------------------------
7175 * A structure for a whole btree index statistics
@@ -102,7 +106,29 @@ typedef struct GinIndexStat
102106 int64 pending_tuples ;
103107} GinIndexStat ;
104108
109+ /* ------------------------------------------------
110+ * A structure for a whole HASH index statistics
111+ * used by pgstathashindex().
112+ * ------------------------------------------------
113+ */
114+ typedef struct HashIndexStat
115+ {
116+ int32 version ;
117+ int32 space_per_page ;
118+
119+ BlockNumber bucket_pages ;
120+ BlockNumber overflow_pages ;
121+ BlockNumber bitmap_pages ;
122+ BlockNumber zero_pages ;
123+
124+ int64 live_items ;
125+ int64 dead_items ;
126+ uint64 free_space ;
127+ } HashIndexStat ;
128+
105129static Datum pgstatindex_impl (Relation rel , FunctionCallInfo fcinfo );
130+ static void GetHashPageStats (Page page , HashIndexStat * stats );
131+
106132
107133/* ------------------------------------------------------
108134 * pgstatindex()
@@ -528,3 +554,172 @@ pgstatginindex_internal(Oid relid, FunctionCallInfo fcinfo)
528554
529555 return (result );
530556}
557+
558+ /* ------------------------------------------------------
559+ * pgstathashindex()
560+ *
561+ * Usage: SELECT * FROM pgstathashindex('hashindex');
562+ * ------------------------------------------------------
563+ */
564+ Datum
565+ pgstathashindex (PG_FUNCTION_ARGS )
566+ {
567+ Oid relid = PG_GETARG_OID (0 );
568+ BlockNumber nblocks ;
569+ BlockNumber blkno ;
570+ Relation rel ;
571+ HashIndexStat stats ;
572+ BufferAccessStrategy bstrategy ;
573+ HeapTuple tuple ;
574+ TupleDesc tupleDesc ;
575+ Datum values [8 ];
576+ bool nulls [8 ];
577+ Buffer metabuf ;
578+ HashMetaPage metap ;
579+ float8 free_percent ;
580+ uint64 total_space ;
581+
582+ rel = index_open (relid , AccessShareLock );
583+
584+ if (!IS_HASH (rel ))
585+ elog (ERROR , "relation \"%s\" is not a HASH index" ,
586+ RelationGetRelationName (rel ));
587+
588+ /*
589+ * Reject attempts to read non-local temporary relations; we would be
590+ * likely to get wrong data since we have no visibility into the owning
591+ * session's local buffers.
592+ */
593+ if (RELATION_IS_OTHER_TEMP (rel ))
594+ ereport (ERROR ,
595+ (errcode (ERRCODE_FEATURE_NOT_SUPPORTED ),
596+ errmsg ("cannot access temporary indexes of other sessions" )));
597+
598+ /* Get the information we need from the metapage. */
599+ memset (& stats , 0 , sizeof (stats ));
600+ metabuf = _hash_getbuf (rel , HASH_METAPAGE , HASH_READ , LH_META_PAGE );
601+ metap = HashPageGetMeta (BufferGetPage (metabuf ));
602+ stats .version = metap -> hashm_version ;
603+ stats .space_per_page = metap -> hashm_bsize ;
604+ _hash_relbuf (rel , metabuf );
605+
606+ /* Get the current relation length */
607+ nblocks = RelationGetNumberOfBlocks (rel );
608+
609+ /* prepare access strategy for this index */
610+ bstrategy = GetAccessStrategy (BAS_BULKREAD );
611+
612+ /* Start from blkno 1 as 0th block is metapage */
613+ for (blkno = 1 ; blkno < nblocks ; blkno ++ )
614+ {
615+ Buffer buf ;
616+ Page page ;
617+ HashPageOpaque opaque ;
618+
619+ CHECK_FOR_INTERRUPTS ();
620+
621+ buf = ReadBufferExtended (rel , MAIN_FORKNUM , blkno , RBM_NORMAL ,
622+ bstrategy );
623+ LockBuffer (buf , BUFFER_LOCK_SHARE );
624+ page = (Page ) BufferGetPage (buf );
625+
626+ if (PageIsNew (page ))
627+ stats .zero_pages ++ ;
628+ else if (PageGetSpecialSize (page ) !=
629+ MAXALIGN (sizeof (HashPageOpaqueData )))
630+ ereport (ERROR ,
631+ (errcode (ERRCODE_INDEX_CORRUPTED ),
632+ errmsg ("index \"%s\" contains corrupted page at block %u" ,
633+ RelationGetRelationName (rel ),
634+ BufferGetBlockNumber (buf ))));
635+ else
636+ {
637+ opaque = (HashPageOpaque ) PageGetSpecialPointer (page );
638+ if (opaque -> hasho_flag & LH_BUCKET_PAGE )
639+ {
640+ stats .bucket_pages ++ ;
641+ GetHashPageStats (page , & stats );
642+ }
643+ else if (opaque -> hasho_flag & LH_OVERFLOW_PAGE )
644+ {
645+ stats .overflow_pages ++ ;
646+ GetHashPageStats (page , & stats );
647+ }
648+ else if (opaque -> hasho_flag & LH_BITMAP_PAGE )
649+ stats .bitmap_pages ++ ;
650+ else
651+ ereport (ERROR ,
652+ (errcode (ERRCODE_INDEX_CORRUPTED ),
653+ errmsg ("unexpected page type 0x%04X in HASH index \"%s\" block %u" ,
654+ opaque -> hasho_flag , RelationGetRelationName (rel ),
655+ BufferGetBlockNumber (buf ))));
656+ }
657+ UnlockReleaseBuffer (buf );
658+ }
659+
660+ /* Done accessing the index */
661+ index_close (rel , AccessShareLock );
662+
663+ /* Count zero pages as free space. */
664+ stats .free_space += stats .zero_pages * stats .space_per_page ;
665+
666+ /*
667+ * Total space available for tuples excludes the metapage and the bitmap
668+ * pages.
669+ */
670+ total_space = (nblocks - (stats .bitmap_pages + 1 )) * stats .space_per_page ;
671+
672+ if (total_space == 0 )
673+ free_percent = 0.0 ;
674+ else
675+ free_percent = 100.0 * stats .free_space / total_space ;
676+
677+ /*
678+ * Build a tuple descriptor for our result type
679+ */
680+ if (get_call_result_type (fcinfo , NULL , & tupleDesc ) != TYPEFUNC_COMPOSITE )
681+ elog (ERROR , "return type must be a row type" );
682+
683+ tupleDesc = BlessTupleDesc (tupleDesc );
684+
685+ /*
686+ * Build and return the tuple
687+ */
688+ MemSet (nulls , 0 , sizeof (nulls ));
689+ values [0 ] = Int32GetDatum (stats .version );
690+ values [1 ] = Int64GetDatum ((int64 ) stats .bucket_pages );
691+ values [2 ] = Int64GetDatum ((int64 ) stats .overflow_pages );
692+ values [3 ] = Int64GetDatum ((int64 ) stats .bitmap_pages );
693+ values [4 ] = Int64GetDatum ((int64 ) stats .zero_pages );
694+ values [5 ] = Int64GetDatum (stats .live_items );
695+ values [6 ] = Int64GetDatum (stats .dead_items );
696+ values [7 ] = Float8GetDatum (free_percent );
697+ tuple = heap_form_tuple (tupleDesc , values , nulls );
698+
699+ PG_RETURN_DATUM (HeapTupleGetDatum (tuple ));
700+ }
701+
702+ /* -------------------------------------------------
703+ * GetHashPageStatis()
704+ *
705+ * Collect statistics of single hash page
706+ * -------------------------------------------------
707+ */
708+ static void
709+ GetHashPageStats (Page page , HashIndexStat * stats )
710+ {
711+ OffsetNumber maxoff = PageGetMaxOffsetNumber (page );
712+ int off ;
713+
714+ /* count live and dead tuples, and free space */
715+ for (off = FirstOffsetNumber ; off <= maxoff ; off ++ )
716+ {
717+ ItemId id = PageGetItemId (page , off );
718+
719+ if (!ItemIdIsDead (id ))
720+ stats -> live_items ++ ;
721+ else
722+ stats -> dead_items ++ ;
723+ }
724+ stats -> free_space += PageGetExactFreeSpace (page );
725+ }
0 commit comments