LCOV - code coverage report
Current view: top level - contrib/pgstattuple - pgstatapprox.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 46 98 46.9 %
Date: 2024-12-02 20:15:07 Functions: 5 6 83.3 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * pgstatapprox.c
       4             :  *        Bloat estimation functions
       5             :  *
       6             :  * Copyright (c) 2014-2024, PostgreSQL Global Development Group
       7             :  *
       8             :  * IDENTIFICATION
       9             :  *        contrib/pgstattuple/pgstatapprox.c
      10             :  *
      11             :  *-------------------------------------------------------------------------
      12             :  */
      13             : #include "postgres.h"
      14             : 
      15             : #include "access/heapam.h"
      16             : #include "access/htup_details.h"
      17             : #include "access/relation.h"
      18             : #include "access/visibilitymap.h"
      19             : #include "catalog/pg_am_d.h"
      20             : #include "commands/vacuum.h"
      21             : #include "funcapi.h"
      22             : #include "miscadmin.h"
      23             : #include "storage/bufmgr.h"
      24             : #include "storage/freespace.h"
      25             : #include "storage/procarray.h"
      26             : 
      27           2 : PG_FUNCTION_INFO_V1(pgstattuple_approx);
      28           4 : PG_FUNCTION_INFO_V1(pgstattuple_approx_v1_5);
      29             : 
      30             : Datum       pgstattuple_approx_internal(Oid relid, FunctionCallInfo fcinfo);
      31             : 
      32             : typedef struct output_type
      33             : {
      34             :     uint64      table_len;
      35             :     double      scanned_percent;
      36             :     uint64      tuple_count;
      37             :     uint64      tuple_len;
      38             :     double      tuple_percent;
      39             :     uint64      dead_tuple_count;
      40             :     uint64      dead_tuple_len;
      41             :     double      dead_tuple_percent;
      42             :     uint64      free_space;
      43             :     double      free_percent;
      44             : } output_type;
      45             : 
      46             : #define NUM_OUTPUT_COLUMNS 10
      47             : 
      48             : /*
      49             :  * This function takes an already open relation and scans its pages,
      50             :  * skipping those that have the corresponding visibility map bit set.
      51             :  * For pages we skip, we find the free space from the free space map
      52             :  * and approximate tuple_len on that basis. For the others, we count
      53             :  * the exact number of dead tuples etc.
      54             :  *
      55             :  * This scan is loosely based on vacuumlazy.c:lazy_scan_heap(), but
      56             :  * we do not try to avoid skipping single pages.
      57             :  */
      58             : static void
      59           4 : statapprox_heap(Relation rel, output_type *stat)
      60             : {
      61             :     BlockNumber scanned,
      62             :                 nblocks,
      63             :                 blkno;
      64           4 :     Buffer      vmbuffer = InvalidBuffer;
      65             :     BufferAccessStrategy bstrategy;
      66             :     TransactionId OldestXmin;
      67             : 
      68           4 :     OldestXmin = GetOldestNonRemovableTransactionId(rel);
      69           4 :     bstrategy = GetAccessStrategy(BAS_BULKREAD);
      70             : 
      71           4 :     nblocks = RelationGetNumberOfBlocks(rel);
      72           4 :     scanned = 0;
      73             : 
      74           4 :     for (blkno = 0; blkno < nblocks; blkno++)
      75             :     {
      76             :         Buffer      buf;
      77             :         Page        page;
      78             :         OffsetNumber offnum,
      79             :                     maxoff;
      80             :         Size        freespace;
      81             : 
      82           0 :         CHECK_FOR_INTERRUPTS();
      83             : 
      84             :         /*
      85             :          * If the page has only visible tuples, then we can find out the free
      86             :          * space from the FSM and move on.
      87             :          */
      88           0 :         if (VM_ALL_VISIBLE(rel, blkno, &vmbuffer))
      89             :         {
      90           0 :             freespace = GetRecordedFreeSpace(rel, blkno);
      91           0 :             stat->tuple_len += BLCKSZ - freespace;
      92           0 :             stat->free_space += freespace;
      93           0 :             continue;
      94             :         }
      95             : 
      96           0 :         buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno,
      97             :                                  RBM_NORMAL, bstrategy);
      98             : 
      99           0 :         LockBuffer(buf, BUFFER_LOCK_SHARE);
     100             : 
     101           0 :         page = BufferGetPage(buf);
     102             : 
     103           0 :         stat->free_space += PageGetExactFreeSpace(page);
     104             : 
     105             :         /* We may count the page as scanned even if it's new/empty */
     106           0 :         scanned++;
     107             : 
     108           0 :         if (PageIsNew(page) || PageIsEmpty(page))
     109             :         {
     110           0 :             UnlockReleaseBuffer(buf);
     111           0 :             continue;
     112             :         }
     113             : 
     114             :         /*
     115             :          * Look at each tuple on the page and decide whether it's live or
     116             :          * dead, then count it and its size. Unlike lazy_scan_heap, we can
     117             :          * afford to ignore problems and special cases.
     118             :          */
     119           0 :         maxoff = PageGetMaxOffsetNumber(page);
     120             : 
     121           0 :         for (offnum = FirstOffsetNumber;
     122             :              offnum <= maxoff;
     123           0 :              offnum = OffsetNumberNext(offnum))
     124             :         {
     125             :             ItemId      itemid;
     126             :             HeapTupleData tuple;
     127             : 
     128           0 :             itemid = PageGetItemId(page, offnum);
     129             : 
     130           0 :             if (!ItemIdIsUsed(itemid) || ItemIdIsRedirected(itemid) ||
     131           0 :                 ItemIdIsDead(itemid))
     132             :             {
     133           0 :                 continue;
     134             :             }
     135             : 
     136             :             Assert(ItemIdIsNormal(itemid));
     137             : 
     138           0 :             ItemPointerSet(&(tuple.t_self), blkno, offnum);
     139             : 
     140           0 :             tuple.t_data = (HeapTupleHeader) PageGetItem(page, itemid);
     141           0 :             tuple.t_len = ItemIdGetLength(itemid);
     142           0 :             tuple.t_tableOid = RelationGetRelid(rel);
     143             : 
     144             :             /*
     145             :              * We follow VACUUM's lead in counting INSERT_IN_PROGRESS tuples
     146             :              * as "dead" while DELETE_IN_PROGRESS tuples are "live".  We don't
     147             :              * bother distinguishing tuples inserted/deleted by our own
     148             :              * transaction.
     149             :              */
     150           0 :             switch (HeapTupleSatisfiesVacuum(&tuple, OldestXmin, buf))
     151             :             {
     152           0 :                 case HEAPTUPLE_LIVE:
     153             :                 case HEAPTUPLE_DELETE_IN_PROGRESS:
     154           0 :                     stat->tuple_len += tuple.t_len;
     155           0 :                     stat->tuple_count++;
     156           0 :                     break;
     157           0 :                 case HEAPTUPLE_DEAD:
     158             :                 case HEAPTUPLE_RECENTLY_DEAD:
     159             :                 case HEAPTUPLE_INSERT_IN_PROGRESS:
     160           0 :                     stat->dead_tuple_len += tuple.t_len;
     161           0 :                     stat->dead_tuple_count++;
     162           0 :                     break;
     163           0 :                 default:
     164           0 :                     elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result");
     165             :                     break;
     166             :             }
     167             :         }
     168             : 
     169           0 :         UnlockReleaseBuffer(buf);
     170             :     }
     171             : 
     172           4 :     stat->table_len = (uint64) nblocks * BLCKSZ;
     173             : 
     174             :     /*
     175             :      * We don't know how many tuples are in the pages we didn't scan, so
     176             :      * extrapolate the live-tuple count to the whole table in the same way
     177             :      * that VACUUM does.  (Like VACUUM, we're not taking a random sample, so
     178             :      * just extrapolating linearly seems unsafe.)  There should be no dead
     179             :      * tuples in all-visible pages, so no correction is needed for that, and
     180             :      * we already accounted for the space in those pages, too.
     181             :      */
     182           8 :     stat->tuple_count = vac_estimate_reltuples(rel, nblocks, scanned,
     183           4 :                                                stat->tuple_count);
     184             : 
     185             :     /* It's not clear if we could get -1 here, but be safe. */
     186           4 :     stat->tuple_count = Max(stat->tuple_count, 0);
     187             : 
     188             :     /*
     189             :      * Calculate percentages if the relation has one or more pages.
     190             :      */
     191           4 :     if (nblocks != 0)
     192             :     {
     193           0 :         stat->scanned_percent = 100.0 * scanned / nblocks;
     194           0 :         stat->tuple_percent = 100.0 * stat->tuple_len / stat->table_len;
     195           0 :         stat->dead_tuple_percent = 100.0 * stat->dead_tuple_len / stat->table_len;
     196           0 :         stat->free_percent = 100.0 * stat->free_space / stat->table_len;
     197             :     }
     198             : 
     199           4 :     if (BufferIsValid(vmbuffer))
     200             :     {
     201           0 :         ReleaseBuffer(vmbuffer);
     202           0 :         vmbuffer = InvalidBuffer;
     203             :     }
     204           4 : }
     205             : 
     206             : /*
     207             :  * Returns estimated live/dead tuple statistics for the given relid.
     208             :  *
     209             :  * The superuser() check here must be kept as the library might be upgraded
     210             :  * without the extension being upgraded, meaning that in pre-1.5 installations
     211             :  * these functions could be called by any user.
     212             :  */
     213             : Datum
     214           0 : pgstattuple_approx(PG_FUNCTION_ARGS)
     215             : {
     216           0 :     Oid         relid = PG_GETARG_OID(0);
     217             : 
     218           0 :     if (!superuser())
     219           0 :         ereport(ERROR,
     220             :                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
     221             :                  errmsg("must be superuser to use pgstattuple functions")));
     222             : 
     223           0 :     PG_RETURN_DATUM(pgstattuple_approx_internal(relid, fcinfo));
     224             : }
     225             : 
     226             : /*
     227             :  * As of pgstattuple version 1.5, we no longer need to check if the user
     228             :  * is a superuser because we REVOKE EXECUTE on the SQL function from PUBLIC.
     229             :  * Users can then grant access to it based on their policies.
     230             :  *
     231             :  * Otherwise identical to pgstattuple_approx (above).
     232             :  */
     233             : Datum
     234          12 : pgstattuple_approx_v1_5(PG_FUNCTION_ARGS)
     235             : {
     236          12 :     Oid         relid = PG_GETARG_OID(0);
     237             : 
     238          12 :     PG_RETURN_DATUM(pgstattuple_approx_internal(relid, fcinfo));
     239             : }
     240             : 
     241             : Datum
     242          12 : pgstattuple_approx_internal(Oid relid, FunctionCallInfo fcinfo)
     243             : {
     244             :     Relation    rel;
     245          12 :     output_type stat = {0};
     246             :     TupleDesc   tupdesc;
     247             :     bool        nulls[NUM_OUTPUT_COLUMNS];
     248             :     Datum       values[NUM_OUTPUT_COLUMNS];
     249             :     HeapTuple   ret;
     250          12 :     int         i = 0;
     251             : 
     252          12 :     if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
     253           0 :         elog(ERROR, "return type must be a row type");
     254             : 
     255          12 :     if (tupdesc->natts != NUM_OUTPUT_COLUMNS)
     256           0 :         elog(ERROR, "incorrect number of output arguments");
     257             : 
     258          12 :     rel = relation_open(relid, AccessShareLock);
     259             : 
     260             :     /*
     261             :      * Reject attempts to read non-local temporary relations; we would be
     262             :      * likely to get wrong data since we have no visibility into the owning
     263             :      * session's local buffers.
     264             :      */
     265          12 :     if (RELATION_IS_OTHER_TEMP(rel))
     266           0 :         ereport(ERROR,
     267             :                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     268             :                  errmsg("cannot access temporary tables of other sessions")));
     269             : 
     270             :     /*
     271             :      * We support only relation kinds with a visibility map and a free space
     272             :      * map.
     273             :      */
     274          12 :     if (!(rel->rd_rel->relkind == RELKIND_RELATION ||
     275          10 :           rel->rd_rel->relkind == RELKIND_MATVIEW ||
     276          10 :           rel->rd_rel->relkind == RELKIND_TOASTVALUE))
     277           8 :         ereport(ERROR,
     278             :                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     279             :                  errmsg("relation \"%s\" is of wrong relation kind",
     280             :                         RelationGetRelationName(rel)),
     281             :                  errdetail_relkind_not_supported(rel->rd_rel->relkind)));
     282             : 
     283           4 :     if (rel->rd_rel->relam != HEAP_TABLE_AM_OID)
     284           0 :         ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
     285             :                         errmsg("only heap AM is supported")));
     286             : 
     287           4 :     statapprox_heap(rel, &stat);
     288             : 
     289           4 :     relation_close(rel, AccessShareLock);
     290             : 
     291           4 :     memset(nulls, 0, sizeof(nulls));
     292             : 
     293           4 :     values[i++] = Int64GetDatum(stat.table_len);
     294           4 :     values[i++] = Float8GetDatum(stat.scanned_percent);
     295           4 :     values[i++] = Int64GetDatum(stat.tuple_count);
     296           4 :     values[i++] = Int64GetDatum(stat.tuple_len);
     297           4 :     values[i++] = Float8GetDatum(stat.tuple_percent);
     298           4 :     values[i++] = Int64GetDatum(stat.dead_tuple_count);
     299           4 :     values[i++] = Int64GetDatum(stat.dead_tuple_len);
     300           4 :     values[i++] = Float8GetDatum(stat.dead_tuple_percent);
     301           4 :     values[i++] = Int64GetDatum(stat.free_space);
     302           4 :     values[i++] = Float8GetDatum(stat.free_percent);
     303             : 
     304           4 :     ret = heap_form_tuple(tupdesc, values, nulls);
     305           4 :     return HeapTupleGetDatum(ret);
     306             : }

Generated by: LCOV version 1.14