Header And Logo

PostgreSQL
| The world's most advanced open source database.

pgstattuple.c

Go to the documentation of this file.
00001 /*
00002  * contrib/pgstattuple/pgstattuple.c
00003  *
00004  * Copyright (c) 2001,2002  Tatsuo Ishii
00005  *
00006  * Permission to use, copy, modify, and distribute this software and
00007  * its documentation for any purpose, without fee, and without a
00008  * written agreement is hereby granted, provided that the above
00009  * copyright notice and this paragraph and the following two
00010  * paragraphs appear in all copies.
00011  *
00012  * IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT,
00013  * INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
00014  * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
00015  * DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED
00016  * OF THE POSSIBILITY OF SUCH DAMAGE.
00017  *
00018  * THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
00019  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
00020  * A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS
00021  * IS" BASIS, AND THE AUTHOR HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE,
00022  * SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
00023  */
00024 
00025 #include "postgres.h"
00026 
00027 #include "access/gist_private.h"
00028 #include "access/hash.h"
00029 #include "access/nbtree.h"
00030 #include "access/relscan.h"
00031 #include "catalog/namespace.h"
00032 #include "funcapi.h"
00033 #include "miscadmin.h"
00034 #include "storage/bufmgr.h"
00035 #include "storage/lmgr.h"
00036 #include "utils/builtins.h"
00037 #include "utils/tqual.h"
00038 
00039 
00040 PG_MODULE_MAGIC;
00041 
00042 PG_FUNCTION_INFO_V1(pgstattuple);
00043 PG_FUNCTION_INFO_V1(pgstattuplebyid);
00044 
00045 extern Datum pgstattuple(PG_FUNCTION_ARGS);
00046 extern Datum pgstattuplebyid(PG_FUNCTION_ARGS);
00047 
00048 /*
00049  * struct pgstattuple_type
00050  *
00051  * tuple_percent, dead_tuple_percent and free_percent are computable,
00052  * so not defined here.
00053  */
00054 typedef struct pgstattuple_type
00055 {
00056     uint64      table_len;
00057     uint64      tuple_count;
00058     uint64      tuple_len;
00059     uint64      dead_tuple_count;
00060     uint64      dead_tuple_len;
00061     uint64      free_space;     /* free/reusable space in bytes */
00062 } pgstattuple_type;
00063 
00064 typedef void (*pgstat_page) (pgstattuple_type *, Relation, BlockNumber,
00065                                          BufferAccessStrategy);
00066 
00067 static Datum build_pgstattuple_type(pgstattuple_type *stat,
00068                        FunctionCallInfo fcinfo);
00069 static Datum pgstat_relation(Relation rel, FunctionCallInfo fcinfo);
00070 static Datum pgstat_heap(Relation rel, FunctionCallInfo fcinfo);
00071 static void pgstat_btree_page(pgstattuple_type *stat,
00072                   Relation rel, BlockNumber blkno,
00073                   BufferAccessStrategy bstrategy);
00074 static void pgstat_hash_page(pgstattuple_type *stat,
00075                  Relation rel, BlockNumber blkno,
00076                  BufferAccessStrategy bstrategy);
00077 static void pgstat_gist_page(pgstattuple_type *stat,
00078                  Relation rel, BlockNumber blkno,
00079                  BufferAccessStrategy bstrategy);
00080 static Datum pgstat_index(Relation rel, BlockNumber start,
00081              pgstat_page pagefn, FunctionCallInfo fcinfo);
00082 static void pgstat_index_page(pgstattuple_type *stat, Page page,
00083                   OffsetNumber minoff, OffsetNumber maxoff);
00084 
00085 /*
00086  * build_pgstattuple_type -- build a pgstattuple_type tuple
00087  */
00088 static Datum
00089 build_pgstattuple_type(pgstattuple_type *stat, FunctionCallInfo fcinfo)
00090 {
00091 #define NCOLUMNS    9
00092 #define NCHARS      32
00093 
00094     HeapTuple   tuple;
00095     char       *values[NCOLUMNS];
00096     char        values_buf[NCOLUMNS][NCHARS];
00097     int         i;
00098     double      tuple_percent;
00099     double      dead_tuple_percent;
00100     double      free_percent;   /* free/reusable space in % */
00101     TupleDesc   tupdesc;
00102     AttInMetadata *attinmeta;
00103 
00104     /* Build a tuple descriptor for our result type */
00105     if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
00106         elog(ERROR, "return type must be a row type");
00107 
00108     /*
00109      * Generate attribute metadata needed later to produce tuples from raw C
00110      * strings
00111      */
00112     attinmeta = TupleDescGetAttInMetadata(tupdesc);
00113 
00114     if (stat->table_len == 0)
00115     {
00116         tuple_percent = 0.0;
00117         dead_tuple_percent = 0.0;
00118         free_percent = 0.0;
00119     }
00120     else
00121     {
00122         tuple_percent = 100.0 * stat->tuple_len / stat->table_len;
00123         dead_tuple_percent = 100.0 * stat->dead_tuple_len / stat->table_len;
00124         free_percent = 100.0 * stat->free_space / stat->table_len;
00125     }
00126 
00127     /*
00128      * Prepare a values array for constructing the tuple. This should be an
00129      * array of C strings which will be processed later by the appropriate
00130      * "in" functions.
00131      */
00132     for (i = 0; i < NCOLUMNS; i++)
00133         values[i] = values_buf[i];
00134     i = 0;
00135     snprintf(values[i++], NCHARS, INT64_FORMAT, stat->table_len);
00136     snprintf(values[i++], NCHARS, INT64_FORMAT, stat->tuple_count);
00137     snprintf(values[i++], NCHARS, INT64_FORMAT, stat->tuple_len);
00138     snprintf(values[i++], NCHARS, "%.2f", tuple_percent);
00139     snprintf(values[i++], NCHARS, INT64_FORMAT, stat->dead_tuple_count);
00140     snprintf(values[i++], NCHARS, INT64_FORMAT, stat->dead_tuple_len);
00141     snprintf(values[i++], NCHARS, "%.2f", dead_tuple_percent);
00142     snprintf(values[i++], NCHARS, INT64_FORMAT, stat->free_space);
00143     snprintf(values[i++], NCHARS, "%.2f", free_percent);
00144 
00145     /* build a tuple */
00146     tuple = BuildTupleFromCStrings(attinmeta, values);
00147 
00148     /* make the tuple into a datum */
00149     return HeapTupleGetDatum(tuple);
00150 }
00151 
00152 /* ----------
00153  * pgstattuple:
00154  * returns live/dead tuples info
00155  *
00156  * C FUNCTION definition
00157  * pgstattuple(text) returns pgstattuple_type
00158  * see pgstattuple.sql for pgstattuple_type
00159  * ----------
00160  */
00161 
00162 Datum
00163 pgstattuple(PG_FUNCTION_ARGS)
00164 {
00165     text       *relname = PG_GETARG_TEXT_P(0);
00166     RangeVar   *relrv;
00167     Relation    rel;
00168 
00169     if (!superuser())
00170         ereport(ERROR,
00171                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
00172                  (errmsg("must be superuser to use pgstattuple functions"))));
00173 
00174     /* open relation */
00175     relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
00176     rel = relation_openrv(relrv, AccessShareLock);
00177 
00178     PG_RETURN_DATUM(pgstat_relation(rel, fcinfo));
00179 }
00180 
00181 Datum
00182 pgstattuplebyid(PG_FUNCTION_ARGS)
00183 {
00184     Oid         relid = PG_GETARG_OID(0);
00185     Relation    rel;
00186 
00187     if (!superuser())
00188         ereport(ERROR,
00189                 (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
00190                  (errmsg("must be superuser to use pgstattuple functions"))));
00191 
00192     /* open relation */
00193     rel = relation_open(relid, AccessShareLock);
00194 
00195     PG_RETURN_DATUM(pgstat_relation(rel, fcinfo));
00196 }
00197 
00198 /*
00199  * pgstat_relation
00200  */
00201 static Datum
00202 pgstat_relation(Relation rel, FunctionCallInfo fcinfo)
00203 {
00204     const char *err;
00205 
00206     /*
00207      * Reject attempts to read non-local temporary relations; we would be
00208      * likely to get wrong data since we have no visibility into the owning
00209      * session's local buffers.
00210      */
00211     if (RELATION_IS_OTHER_TEMP(rel))
00212         ereport(ERROR,
00213                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
00214                  errmsg("cannot access temporary tables of other sessions")));
00215 
00216     switch (rel->rd_rel->relkind)
00217     {
00218         case RELKIND_RELATION:
00219         case RELKIND_MATVIEW:
00220         case RELKIND_TOASTVALUE:
00221         case RELKIND_SEQUENCE:
00222             return pgstat_heap(rel, fcinfo);
00223         case RELKIND_INDEX:
00224             switch (rel->rd_rel->relam)
00225             {
00226                 case BTREE_AM_OID:
00227                     return pgstat_index(rel, BTREE_METAPAGE + 1,
00228                                         pgstat_btree_page, fcinfo);
00229                 case HASH_AM_OID:
00230                     return pgstat_index(rel, HASH_METAPAGE + 1,
00231                                         pgstat_hash_page, fcinfo);
00232                 case GIST_AM_OID:
00233                     return pgstat_index(rel, GIST_ROOT_BLKNO + 1,
00234                                         pgstat_gist_page, fcinfo);
00235                 case GIN_AM_OID:
00236                     err = "gin index";
00237                     break;
00238                 case SPGIST_AM_OID:
00239                     err = "spgist index";
00240                     break;
00241                 default:
00242                     err = "unknown index";
00243                     break;
00244             }
00245             break;
00246         case RELKIND_VIEW:
00247             err = "view";
00248             break;
00249         case RELKIND_COMPOSITE_TYPE:
00250             err = "composite type";
00251             break;
00252         case RELKIND_FOREIGN_TABLE:
00253             err = "foreign table";
00254             break;
00255         default:
00256             err = "unknown";
00257             break;
00258     }
00259 
00260     ereport(ERROR,
00261             (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
00262              errmsg("\"%s\" (%s) is not supported",
00263                     RelationGetRelationName(rel), err)));
00264     return 0;                   /* should not happen */
00265 }
00266 
00267 /*
00268  * pgstat_heap -- returns live/dead tuples info in a heap
00269  */
00270 static Datum
00271 pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
00272 {
00273     HeapScanDesc scan;
00274     HeapTuple   tuple;
00275     BlockNumber nblocks;
00276     BlockNumber block = 0;      /* next block to count free space in */
00277     BlockNumber tupblock;
00278     Buffer      buffer;
00279     pgstattuple_type stat = {0};
00280     BufferAccessStrategy bstrategy;
00281 
00282     /* Disable syncscan because we assume we scan from block zero upwards */
00283     scan = heap_beginscan_strat(rel, SnapshotAny, 0, NULL, true, false);
00284 
00285     nblocks = scan->rs_nblocks; /* # blocks to be scanned */
00286 
00287     /* prepare access strategy for this table */
00288     bstrategy = GetAccessStrategy(BAS_BULKREAD);
00289     scan->rs_strategy = bstrategy;
00290 
00291     /* scan the relation */
00292     while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
00293     {
00294         CHECK_FOR_INTERRUPTS();
00295 
00296         /* must hold a buffer lock to call HeapTupleSatisfiesVisibility */
00297         LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE);
00298 
00299         if (HeapTupleSatisfiesVisibility(tuple, SnapshotNow, scan->rs_cbuf))
00300         {
00301             stat.tuple_len += tuple->t_len;
00302             stat.tuple_count++;
00303         }
00304         else
00305         {
00306             stat.dead_tuple_len += tuple->t_len;
00307             stat.dead_tuple_count++;
00308         }
00309 
00310         LockBuffer(scan->rs_cbuf, BUFFER_LOCK_UNLOCK);
00311 
00312         /*
00313          * To avoid physically reading the table twice, try to do the
00314          * free-space scan in parallel with the heap scan.  However,
00315          * heap_getnext may find no tuples on a given page, so we cannot
00316          * simply examine the pages returned by the heap scan.
00317          */
00318         tupblock = BlockIdGetBlockNumber(&tuple->t_self.ip_blkid);
00319 
00320         while (block <= tupblock)
00321         {
00322             CHECK_FOR_INTERRUPTS();
00323 
00324             buffer = ReadBufferExtended(rel, MAIN_FORKNUM, block, RBM_NORMAL, bstrategy);
00325             LockBuffer(buffer, BUFFER_LOCK_SHARE);
00326             stat.free_space += PageGetHeapFreeSpace((Page) BufferGetPage(buffer));
00327             UnlockReleaseBuffer(buffer);
00328             block++;
00329         }
00330     }
00331     heap_endscan(scan);
00332 
00333     while (block < nblocks)
00334     {
00335         CHECK_FOR_INTERRUPTS();
00336 
00337         buffer = ReadBufferExtended(rel, MAIN_FORKNUM, block, RBM_NORMAL, bstrategy);
00338         LockBuffer(buffer, BUFFER_LOCK_SHARE);
00339         stat.free_space += PageGetHeapFreeSpace((Page) BufferGetPage(buffer));
00340         UnlockReleaseBuffer(buffer);
00341         block++;
00342     }
00343 
00344     relation_close(rel, AccessShareLock);
00345 
00346     stat.table_len = (uint64) nblocks *BLCKSZ;
00347 
00348     return build_pgstattuple_type(&stat, fcinfo);
00349 }
00350 
00351 /*
00352  * pgstat_btree_page -- check tuples in a btree page
00353  */
00354 static void
00355 pgstat_btree_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno,
00356                   BufferAccessStrategy bstrategy)
00357 {
00358     Buffer      buf;
00359     Page        page;
00360 
00361     buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, bstrategy);
00362     LockBuffer(buf, BT_READ);
00363     page = BufferGetPage(buf);
00364 
00365     /* Page is valid, see what to do with it */
00366     if (PageIsNew(page))
00367     {
00368         /* fully empty page */
00369         stat->free_space += BLCKSZ;
00370     }
00371     else
00372     {
00373         BTPageOpaque opaque;
00374 
00375         opaque = (BTPageOpaque) PageGetSpecialPointer(page);
00376         if (opaque->btpo_flags & (BTP_DELETED | BTP_HALF_DEAD))
00377         {
00378             /* recyclable page */
00379             stat->free_space += BLCKSZ;
00380         }
00381         else if (P_ISLEAF(opaque))
00382         {
00383             pgstat_index_page(stat, page, P_FIRSTDATAKEY(opaque),
00384                               PageGetMaxOffsetNumber(page));
00385         }
00386         else
00387         {
00388             /* root or node */
00389         }
00390     }
00391 
00392     _bt_relbuf(rel, buf);
00393 }
00394 
00395 /*
00396  * pgstat_hash_page -- check tuples in a hash page
00397  */
00398 static void
00399 pgstat_hash_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno,
00400                  BufferAccessStrategy bstrategy)
00401 {
00402     Buffer      buf;
00403     Page        page;
00404 
00405     _hash_getlock(rel, blkno, HASH_SHARE);
00406     buf = _hash_getbuf_with_strategy(rel, blkno, HASH_READ, 0, bstrategy);
00407     page = BufferGetPage(buf);
00408 
00409     if (PageGetSpecialSize(page) == MAXALIGN(sizeof(HashPageOpaqueData)))
00410     {
00411         HashPageOpaque opaque;
00412 
00413         opaque = (HashPageOpaque) PageGetSpecialPointer(page);
00414         switch (opaque->hasho_flag)
00415         {
00416             case LH_UNUSED_PAGE:
00417                 stat->free_space += BLCKSZ;
00418                 break;
00419             case LH_BUCKET_PAGE:
00420             case LH_OVERFLOW_PAGE:
00421                 pgstat_index_page(stat, page, FirstOffsetNumber,
00422                                   PageGetMaxOffsetNumber(page));
00423                 break;
00424             case LH_BITMAP_PAGE:
00425             case LH_META_PAGE:
00426             default:
00427                 break;
00428         }
00429     }
00430     else
00431     {
00432         /* maybe corrupted */
00433     }
00434 
00435     _hash_relbuf(rel, buf);
00436     _hash_droplock(rel, blkno, HASH_SHARE);
00437 }
00438 
00439 /*
00440  * pgstat_gist_page -- check tuples in a gist page
00441  */
00442 static void
00443 pgstat_gist_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno,
00444                  BufferAccessStrategy bstrategy)
00445 {
00446     Buffer      buf;
00447     Page        page;
00448 
00449     buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, bstrategy);
00450     LockBuffer(buf, GIST_SHARE);
00451     gistcheckpage(rel, buf);
00452     page = BufferGetPage(buf);
00453 
00454     if (GistPageIsLeaf(page))
00455     {
00456         pgstat_index_page(stat, page, FirstOffsetNumber,
00457                           PageGetMaxOffsetNumber(page));
00458     }
00459     else
00460     {
00461         /* root or node */
00462     }
00463 
00464     UnlockReleaseBuffer(buf);
00465 }
00466 
00467 /*
00468  * pgstat_index -- returns live/dead tuples info in a generic index
00469  */
00470 static Datum
00471 pgstat_index(Relation rel, BlockNumber start, pgstat_page pagefn,
00472              FunctionCallInfo fcinfo)
00473 {
00474     BlockNumber nblocks;
00475     BlockNumber blkno;
00476     BufferAccessStrategy bstrategy;
00477     pgstattuple_type stat = {0};
00478 
00479     /* prepare access strategy for this index */
00480     bstrategy = GetAccessStrategy(BAS_BULKREAD);
00481 
00482     blkno = start;
00483     for (;;)
00484     {
00485         /* Get the current relation length */
00486         LockRelationForExtension(rel, ExclusiveLock);
00487         nblocks = RelationGetNumberOfBlocks(rel);
00488         UnlockRelationForExtension(rel, ExclusiveLock);
00489 
00490         /* Quit if we've scanned the whole relation */
00491         if (blkno >= nblocks)
00492         {
00493             stat.table_len = (uint64) nblocks *BLCKSZ;
00494 
00495             break;
00496         }
00497 
00498         for (; blkno < nblocks; blkno++)
00499         {
00500             CHECK_FOR_INTERRUPTS();
00501 
00502             pagefn(&stat, rel, blkno, bstrategy);
00503         }
00504     }
00505 
00506     relation_close(rel, AccessShareLock);
00507 
00508     return build_pgstattuple_type(&stat, fcinfo);
00509 }
00510 
00511 /*
00512  * pgstat_index_page -- for generic index page
00513  */
00514 static void
00515 pgstat_index_page(pgstattuple_type *stat, Page page,
00516                   OffsetNumber minoff, OffsetNumber maxoff)
00517 {
00518     OffsetNumber i;
00519 
00520     stat->free_space += PageGetFreeSpace(page);
00521 
00522     for (i = minoff; i <= maxoff; i = OffsetNumberNext(i))
00523     {
00524         ItemId      itemid = PageGetItemId(page, i);
00525 
00526         if (ItemIdIsDead(itemid))
00527         {
00528             stat->dead_tuple_count++;
00529             stat->dead_tuple_len += ItemIdGetLength(itemid);
00530         }
00531         else
00532         {
00533             stat->tuple_count++;
00534             stat->tuple_len += ItemIdGetLength(itemid);
00535         }
00536     }
00537 }