Age Owner TLA Line data Source code
1 : /*
2 : * contrib/pgstattuple/pgstatindex.c
3 : *
4 : *
5 : * pgstatindex
6 : *
7 : * Copyright (c) 2006 Satoshi Nagayasu <nagayasus@nttdata.co.jp>
8 : *
9 : * Permission to use, copy, modify, and distribute this software and
10 : * its documentation for any purpose, without fee, and without a
11 : * written agreement is hereby granted, provided that the above
12 : * copyright notice and this paragraph and the following two
13 : * paragraphs appear in all copies.
14 : *
15 : * IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT,
16 : * INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
17 : * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
18 : * DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED
19 : * OF THE POSSIBILITY OF SUCH DAMAGE.
20 : *
21 : * THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
22 : * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23 : * A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS
24 : * IS" BASIS, AND THE AUTHOR HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE,
25 : * SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
26 : */
27 :
28 : #include "postgres.h"
29 :
30 : #include "access/gin_private.h"
31 : #include "access/hash.h"
32 : #include "access/htup_details.h"
33 : #include "access/nbtree.h"
34 : #include "access/relation.h"
35 : #include "access/table.h"
36 : #include "catalog/namespace.h"
37 : #include "catalog/pg_am.h"
38 : #include "funcapi.h"
39 : #include "miscadmin.h"
40 : #include "storage/bufmgr.h"
41 : #include "storage/lmgr.h"
42 : #include "utils/builtins.h"
43 : #include "utils/rel.h"
44 : #include "utils/varlena.h"
45 :
46 :
47 : /*
48 : * Because of backward-compatibility issue, we have decided to have
49 : * two types of interfaces, with regclass-type input arg and text-type
50 : * input arg, for each function.
51 : *
52 : * Those functions which have text-type input arg will be deprecated
53 : * in the future release.
54 : */
5705 tgl 55 CBC 1 : PG_FUNCTION_INFO_V1(pgstatindex);
3551 fujii 56 1 : PG_FUNCTION_INFO_V1(pgstatindexbyid);
5705 tgl 57 1 : PG_FUNCTION_INFO_V1(pg_relpages);
3551 fujii 58 1 : PG_FUNCTION_INFO_V1(pg_relpagesbyid);
3777 heikki.linnakangas 59 1 : PG_FUNCTION_INFO_V1(pgstatginindex);
2256 rhaas 60 2 : PG_FUNCTION_INFO_V1(pgstathashindex);
61 :
2383 sfrost 62 2 : PG_FUNCTION_INFO_V1(pgstatindex_v1_5);
63 2 : PG_FUNCTION_INFO_V1(pgstatindexbyid_v1_5);
64 2 : PG_FUNCTION_INFO_V1(pg_relpages_v1_5);
65 2 : PG_FUNCTION_INFO_V1(pg_relpagesbyid_v1_5);
66 2 : PG_FUNCTION_INFO_V1(pgstatginindex_v1_5);
67 :
68 : Datum pgstatginindex_internal(Oid relid, FunctionCallInfo fcinfo);
69 :
70 : #define IS_INDEX(r) ((r)->rd_rel->relkind == RELKIND_INDEX)
71 : #define IS_BTREE(r) ((r)->rd_rel->relam == BTREE_AM_OID)
72 : #define IS_GIN(r) ((r)->rd_rel->relam == GIN_AM_OID)
73 : #define IS_HASH(r) ((r)->rd_rel->relam == HASH_AM_OID)
74 :
75 : /* ------------------------------------------------
76 : * A structure for a whole btree index statistics
77 : * used by pgstatindex().
78 : * ------------------------------------------------
79 : */
80 : typedef struct BTIndexStat
81 : {
82 : uint32 version;
83 : uint32 level;
84 : BlockNumber root_blkno;
85 :
86 : uint64 internal_pages;
87 : uint64 leaf_pages;
88 : uint64 empty_pages;
89 : uint64 deleted_pages;
90 :
91 : uint64 max_avail;
92 : uint64 free_space;
93 :
94 : uint64 fragments;
95 : } BTIndexStat;
96 :
97 : /* ------------------------------------------------
98 : * A structure for a whole GIN index statistics
99 : * used by pgstatginindex().
100 : * ------------------------------------------------
101 : */
102 : typedef struct GinIndexStat
103 : {
104 : int32 version;
105 :
106 : BlockNumber pending_pages;
107 : int64 pending_tuples;
108 : } GinIndexStat;
109 :
110 : /* ------------------------------------------------
111 : * A structure for a whole HASH index statistics
112 : * used by pgstathashindex().
113 : * ------------------------------------------------
114 : */
115 : typedef struct HashIndexStat
116 : {
117 : int32 version;
118 : int32 space_per_page;
119 :
120 : BlockNumber bucket_pages;
121 : BlockNumber overflow_pages;
122 : BlockNumber bitmap_pages;
123 : BlockNumber unused_pages;
124 :
125 : int64 live_items;
126 : int64 dead_items;
127 : uint64 free_space;
128 : } HashIndexStat;
129 :
130 : static Datum pgstatindex_impl(Relation rel, FunctionCallInfo fcinfo);
131 : static int64 pg_relpages_impl(Relation rel);
132 : static void GetHashPageStats(Page page, HashIndexStat *stats);
133 :
134 : /* ------------------------------------------------------
135 : * pgstatindex()
136 : *
137 : * Usage: SELECT * FROM pgstatindex('t1_pkey');
138 : *
139 : * The superuser() check here must be kept as the library might be upgraded
140 : * without the extension being upgraded, meaning that in pre-1.5 installations
141 : * these functions could be called by any user.
142 : * ------------------------------------------------------
143 : */
144 : Datum
6063 bruce 145 UBC 0 : pgstatindex(PG_FUNCTION_ARGS)
146 : {
2219 noah 147 0 : text *relname = PG_GETARG_TEXT_PP(0);
148 : Relation rel;
149 : RangeVar *relrv;
150 :
5705 tgl 151 0 : if (!superuser())
152 0 : ereport(ERROR,
153 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
154 : errmsg("must be superuser to use pgstattuple functions")));
155 :
6063 bruce 156 0 : relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
157 0 : rel = relation_openrv(relrv, AccessShareLock);
158 :
3551 fujii 159 0 : PG_RETURN_DATUM(pgstatindex_impl(rel, fcinfo));
160 : }
161 :
162 : /*
163 : * As of pgstattuple version 1.5, we no longer need to check if the user
164 : * is a superuser because we REVOKE EXECUTE on the function from PUBLIC.
165 : * Users can then grant access to it based on their policies.
166 : *
167 : * Otherwise identical to pgstatindex (above).
168 : */
169 : Datum
2383 sfrost 170 CBC 10 : pgstatindex_v1_5(PG_FUNCTION_ARGS)
171 : {
2219 noah 172 10 : text *relname = PG_GETARG_TEXT_PP(0);
173 : Relation rel;
174 : RangeVar *relrv;
175 :
2383 sfrost 176 10 : relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
177 10 : rel = relation_openrv(relrv, AccessShareLock);
178 :
179 10 : PG_RETURN_DATUM(pgstatindex_impl(rel, fcinfo));
180 : }
181 :
182 : /*
183 : * The superuser() check here must be kept as the library might be upgraded
184 : * without the extension being upgraded, meaning that in pre-1.5 installations
185 : * these functions could be called by any user.
186 : */
187 : Datum
3551 fujii 188 UBC 0 : pgstatindexbyid(PG_FUNCTION_ARGS)
189 : {
190 0 : Oid relid = PG_GETARG_OID(0);
191 : Relation rel;
192 :
193 0 : if (!superuser())
194 0 : ereport(ERROR,
195 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
196 : errmsg("must be superuser to use pgstattuple functions")));
197 :
198 0 : rel = relation_open(relid, AccessShareLock);
199 :
200 0 : PG_RETURN_DATUM(pgstatindex_impl(rel, fcinfo));
201 : }
202 :
203 : /* No need for superuser checks in v1.5, see above */
204 : Datum
2383 sfrost 205 CBC 1 : pgstatindexbyid_v1_5(PG_FUNCTION_ARGS)
206 : {
207 1 : Oid relid = PG_GETARG_OID(0);
208 : Relation rel;
209 :
210 1 : rel = relation_open(relid, AccessShareLock);
211 :
212 1 : PG_RETURN_DATUM(pgstatindex_impl(rel, fcinfo));
213 : }
214 :
215 : static Datum
3551 fujii 216 11 : pgstatindex_impl(Relation rel, FunctionCallInfo fcinfo)
217 : {
218 : Datum result;
219 : BlockNumber nblocks;
220 : BlockNumber blkno;
221 : BTIndexStat indexStat;
222 11 : BufferAccessStrategy bstrategy = GetAccessStrategy(BAS_BULKREAD);
223 :
6063 bruce 224 11 : if (!IS_INDEX(rel) || !IS_BTREE(rel))
2222 sfrost 225 6 : ereport(ERROR,
226 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
227 : errmsg("relation \"%s\" is not a btree index",
228 : RelationGetRelationName(rel))));
229 :
230 : /*
231 : * Reject attempts to read non-local temporary relations; we would be
232 : * likely to get wrong data since we have no visibility into the owning
233 : * session's local buffers.
234 : */
5122 tgl 235 5 : if (RELATION_IS_OTHER_TEMP(rel))
5122 tgl 236 UBC 0 : ereport(ERROR,
237 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
238 : errmsg("cannot access temporary tables of other sessions")));
239 :
240 : /*
241 : * Read metapage
242 : */
243 : {
4044 rhaas 244 CBC 5 : Buffer buffer = ReadBufferExtended(rel, MAIN_FORKNUM, 0, RBM_NORMAL, bstrategy);
2545 kgrittn 245 5 : Page page = BufferGetPage(buffer);
6063 bruce 246 5 : BTMetaPageData *metad = BTPageGetMeta(page);
247 :
248 5 : indexStat.version = metad->btm_version;
249 5 : indexStat.level = metad->btm_level;
5497 tgl 250 5 : indexStat.root_blkno = metad->btm_root;
251 :
6063 bruce 252 5 : ReleaseBuffer(buffer);
253 : }
254 :
255 : /* -- init counters -- */
256 5 : indexStat.internal_pages = 0;
5497 tgl 257 5 : indexStat.leaf_pages = 0;
6063 bruce 258 5 : indexStat.empty_pages = 0;
259 5 : indexStat.deleted_pages = 0;
260 :
261 5 : indexStat.max_avail = 0;
262 5 : indexStat.free_space = 0;
263 :
5497 tgl 264 5 : indexStat.fragments = 0;
265 :
266 : /*
267 : * Scan all blocks except the metapage
268 : */
269 5 : nblocks = RelationGetNumberOfBlocks(rel);
270 :
6063 bruce 271 5 : for (blkno = 1; blkno < nblocks; blkno++)
272 : {
273 : Buffer buffer;
274 : Page page;
275 : BTPageOpaque opaque;
276 :
4203 rhaas 277 UBC 0 : CHECK_FOR_INTERRUPTS();
278 :
279 : /* Read and lock buffer */
3955 bruce 280 0 : buffer = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, bstrategy);
5806 281 0 : LockBuffer(buffer, BUFFER_LOCK_SHARE);
282 :
2545 kgrittn 283 0 : page = BufferGetPage(buffer);
373 michael 284 0 : opaque = BTPageGetOpaque(page);
285 :
286 : /*
287 : * Determine page type, and update totals.
288 : *
289 : * Note that we arbitrarily bucket deleted pages together without
290 : * considering if they're leaf pages or internal pages.
291 : */
2607 tgl 292 0 : if (P_ISDELETED(opaque))
293 0 : indexStat.deleted_pages++;
294 0 : else if (P_IGNORE(opaque))
295 0 : indexStat.empty_pages++; /* this is the "half dead" state */
296 0 : else if (P_ISLEAF(opaque))
297 : {
298 : int max_avail;
299 :
5624 bruce 300 0 : max_avail = BLCKSZ - (BLCKSZ - ((PageHeader) page)->pd_special + SizeOfPageHeaderData);
5806 301 0 : indexStat.max_avail += max_avail;
302 0 : indexStat.free_space += PageGetFreeSpace(page);
303 :
304 0 : indexStat.leaf_pages++;
305 :
306 : /*
307 : * If the next leaf is on an earlier block, it means a
308 : * fragmentation.
309 : */
310 0 : if (opaque->btpo_next != P_NONE && opaque->btpo_next < blkno)
311 0 : indexStat.fragments++;
312 : }
313 : else
314 0 : indexStat.internal_pages++;
315 :
316 : /* Unlock and release buffer */
317 0 : LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
6063 318 0 : ReleaseBuffer(buffer);
319 : }
320 :
6063 bruce 321 CBC 5 : relation_close(rel, AccessShareLock);
322 :
323 : /*----------------------------
324 : * Build a result tuple
325 : *----------------------------
326 : */
327 : {
328 : TupleDesc tupleDesc;
329 : int j;
330 : char *values[10];
331 : HeapTuple tuple;
332 :
333 : /* Build a tuple descriptor for our result type */
5705 tgl 334 5 : if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
5705 tgl 335 UBC 0 : elog(ERROR, "return type must be a row type");
336 :
6063 bruce 337 CBC 5 : j = 0;
3380 peter_e 338 5 : values[j++] = psprintf("%d", indexStat.version);
339 5 : values[j++] = psprintf("%d", indexStat.level);
340 10 : values[j++] = psprintf(INT64_FORMAT,
341 : (1 + /* include the metapage in index_size */
3260 bruce 342 5 : indexStat.leaf_pages +
343 5 : indexStat.internal_pages +
344 5 : indexStat.deleted_pages +
345 5 : indexStat.empty_pages) * BLCKSZ);
3380 peter_e 346 5 : values[j++] = psprintf("%u", indexStat.root_blkno);
347 5 : values[j++] = psprintf(INT64_FORMAT, indexStat.internal_pages);
348 5 : values[j++] = psprintf(INT64_FORMAT, indexStat.leaf_pages);
349 5 : values[j++] = psprintf(INT64_FORMAT, indexStat.empty_pages);
350 5 : values[j++] = psprintf(INT64_FORMAT, indexStat.deleted_pages);
4246 tgl 351 5 : if (indexStat.max_avail > 0)
3380 peter_e 352 UBC 0 : values[j++] = psprintf("%.2f",
3260 bruce 353 0 : 100.0 - (double) indexStat.free_space / (double) indexStat.max_avail * 100.0);
354 : else
3380 peter_e 355 CBC 5 : values[j++] = pstrdup("NaN");
4246 tgl 356 5 : if (indexStat.leaf_pages > 0)
3380 peter_e 357 UBC 0 : values[j++] = psprintf("%.2f",
3260 bruce 358 0 : (double) indexStat.fragments / (double) indexStat.leaf_pages * 100.0);
359 : else
3380 peter_e 360 CBC 5 : values[j++] = pstrdup("NaN");
361 :
6063 bruce 362 5 : tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc),
363 : values);
364 :
5705 tgl 365 5 : result = HeapTupleGetDatum(tuple);
366 : }
367 :
3551 fujii 368 5 : return result;
369 : }
370 :
371 : /* --------------------------------------------------------
372 : * pg_relpages()
373 : *
374 : * Get the number of pages of the table/index.
375 : *
376 : * Usage: SELECT pg_relpages('t1');
377 : * SELECT pg_relpages('t1_pkey');
378 : *
379 : * Must keep superuser() check, see above.
380 : * --------------------------------------------------------
381 : */
382 : Datum
6063 bruce 383 UBC 0 : pg_relpages(PG_FUNCTION_ARGS)
384 : {
2219 noah 385 0 : text *relname = PG_GETARG_TEXT_PP(0);
386 : Relation rel;
387 : RangeVar *relrv;
388 :
5705 tgl 389 0 : if (!superuser())
390 0 : ereport(ERROR,
391 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
392 : errmsg("must be superuser to use pgstattuple functions")));
393 :
6063 bruce 394 0 : relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
395 0 : rel = relation_openrv(relrv, AccessShareLock);
396 :
640 peter 397 0 : PG_RETURN_INT64(pg_relpages_impl(rel));
398 : }
399 :
400 : /* No need for superuser checks in v1.5, see above */
401 : Datum
2383 sfrost 402 CBC 9 : pg_relpages_v1_5(PG_FUNCTION_ARGS)
403 : {
2219 noah 404 9 : text *relname = PG_GETARG_TEXT_PP(0);
405 : Relation rel;
406 : RangeVar *relrv;
407 :
2383 sfrost 408 9 : relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
409 9 : rel = relation_openrv(relrv, AccessShareLock);
410 :
640 peter 411 9 : PG_RETURN_INT64(pg_relpages_impl(rel));
412 : }
413 :
414 : /* Must keep superuser() check, see above. */
415 : Datum
3551 fujii 416 UBC 0 : pg_relpagesbyid(PG_FUNCTION_ARGS)
417 : {
418 0 : Oid relid = PG_GETARG_OID(0);
419 : Relation rel;
420 :
421 0 : if (!superuser())
422 0 : ereport(ERROR,
423 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
424 : errmsg("must be superuser to use pgstattuple functions")));
425 :
426 0 : rel = relation_open(relid, AccessShareLock);
427 :
640 peter 428 0 : PG_RETURN_INT64(pg_relpages_impl(rel));
429 : }
430 :
431 : /* No need for superuser checks in v1.5, see above */
432 : Datum
2383 sfrost 433 CBC 3 : pg_relpagesbyid_v1_5(PG_FUNCTION_ARGS)
434 : {
435 3 : Oid relid = PG_GETARG_OID(0);
436 : Relation rel;
437 :
438 3 : rel = relation_open(relid, AccessShareLock);
439 :
640 peter 440 3 : PG_RETURN_INT64(pg_relpages_impl(rel));
441 : }
442 :
443 : static int64
444 12 : pg_relpages_impl(Relation rel)
445 : {
446 : int64 relpages;
447 :
448 12 : if (!RELKIND_HAS_STORAGE(rel->rd_rel->relkind))
449 3 : ereport(ERROR,
450 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
451 : errmsg("cannot get page count of relation \"%s\"",
452 : RelationGetRelationName(rel)),
453 : errdetail_relkind_not_supported(rel->rd_rel->relkind)));
454 :
455 : /* note: this will work OK on non-local temp tables */
456 :
2383 sfrost 457 9 : relpages = RelationGetNumberOfBlocks(rel);
458 :
459 9 : relation_close(rel, AccessShareLock);
460 :
640 peter 461 9 : return relpages;
462 : }
463 :
464 : /* ------------------------------------------------------
465 : * pgstatginindex()
466 : *
467 : * Usage: SELECT * FROM pgstatginindex('ginindex');
468 : *
469 : * Must keep superuser() check, see above.
470 : * ------------------------------------------------------
471 : */
472 : Datum
3777 heikki.linnakangas 473 UBC 0 : pgstatginindex(PG_FUNCTION_ARGS)
474 : {
475 0 : Oid relid = PG_GETARG_OID(0);
476 :
2383 sfrost 477 0 : if (!superuser())
478 0 : ereport(ERROR,
479 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
480 : errmsg("must be superuser to use pgstattuple functions")));
481 :
482 0 : PG_RETURN_DATUM(pgstatginindex_internal(relid, fcinfo));
483 : }
484 :
485 : /* No need for superuser checks in v1.5, see above */
486 : Datum
2383 sfrost 487 CBC 7 : pgstatginindex_v1_5(PG_FUNCTION_ARGS)
488 : {
489 7 : Oid relid = PG_GETARG_OID(0);
490 :
491 7 : PG_RETURN_DATUM(pgstatginindex_internal(relid, fcinfo));
492 : }
493 :
494 : Datum
495 7 : pgstatginindex_internal(Oid relid, FunctionCallInfo fcinfo)
496 : {
497 : Relation rel;
498 : Buffer buffer;
499 : Page page;
500 : GinMetaPageData *metadata;
501 : GinIndexStat stats;
502 : HeapTuple tuple;
503 : TupleDesc tupleDesc;
504 : Datum values[3];
3777 heikki.linnakangas 505 7 : bool nulls[3] = {false, false, false};
506 : Datum result;
507 :
508 7 : rel = relation_open(relid, AccessShareLock);
509 :
510 7 : if (!IS_INDEX(rel) || !IS_GIN(rel))
2222 sfrost 511 6 : ereport(ERROR,
512 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
513 : errmsg("relation \"%s\" is not a GIN index",
514 : RelationGetRelationName(rel))));
515 :
516 : /*
517 : * Reject attempts to read non-local temporary relations; we would be
518 : * likely to get wrong data since we have no visibility into the owning
519 : * session's local buffers.
520 : */
3777 heikki.linnakangas 521 1 : if (RELATION_IS_OTHER_TEMP(rel))
3777 heikki.linnakangas 522 UBC 0 : ereport(ERROR,
523 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
524 : errmsg("cannot access temporary indexes of other sessions")));
525 :
526 : /*
527 : * Read metapage
528 : */
3777 heikki.linnakangas 529 CBC 1 : buffer = ReadBuffer(rel, GIN_METAPAGE_BLKNO);
530 1 : LockBuffer(buffer, GIN_SHARE);
2545 kgrittn 531 1 : page = BufferGetPage(buffer);
3777 heikki.linnakangas 532 1 : metadata = GinPageGetMeta(page);
533 :
534 1 : stats.version = metadata->ginVersion;
535 1 : stats.pending_pages = metadata->nPendingPages;
536 1 : stats.pending_tuples = metadata->nPendingHeapTuples;
537 :
538 1 : UnlockReleaseBuffer(buffer);
539 1 : relation_close(rel, AccessShareLock);
540 :
541 : /*
542 : * Build a tuple descriptor for our result type
543 : */
544 1 : if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
3777 heikki.linnakangas 545 UBC 0 : elog(ERROR, "return type must be a row type");
546 :
3777 heikki.linnakangas 547 CBC 1 : values[0] = Int32GetDatum(stats.version);
548 1 : values[1] = UInt32GetDatum(stats.pending_pages);
549 1 : values[2] = Int64GetDatum(stats.pending_tuples);
550 :
551 : /*
552 : * Build and return the tuple
553 : */
554 1 : tuple = heap_form_tuple(tupleDesc, values, nulls);
555 1 : result = HeapTupleGetDatum(tuple);
556 :
2061 peter_e 557 1 : return result;
558 : }
559 :
560 : /* ------------------------------------------------------
561 : * pgstathashindex()
562 : *
563 : * Usage: SELECT * FROM pgstathashindex('hashindex');
564 : * ------------------------------------------------------
565 : */
566 : Datum
2256 rhaas 567 8 : pgstathashindex(PG_FUNCTION_ARGS)
568 : {
569 8 : Oid relid = PG_GETARG_OID(0);
570 : BlockNumber nblocks;
571 : BlockNumber blkno;
572 : Relation rel;
573 : HashIndexStat stats;
574 : BufferAccessStrategy bstrategy;
575 : HeapTuple tuple;
576 : TupleDesc tupleDesc;
577 : Datum values[8];
267 peter 578 GNC 8 : bool nulls[8] = {0};
579 : Buffer metabuf;
580 : HashMetaPage metap;
581 : float8 free_percent;
582 : uint64 total_space;
583 :
2256 rhaas 584 CBC 8 : rel = index_open(relid, AccessShareLock);
585 :
586 : /* index_open() checks that it's an index */
587 4 : if (!IS_HASH(rel))
2222 sfrost 588 2 : ereport(ERROR,
589 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
590 : errmsg("relation \"%s\" is not a hash index",
591 : RelationGetRelationName(rel))));
592 :
593 : /*
594 : * Reject attempts to read non-local temporary relations; we would be
595 : * likely to get wrong data since we have no visibility into the owning
596 : * session's local buffers.
597 : */
2256 rhaas 598 2 : if (RELATION_IS_OTHER_TEMP(rel))
2256 rhaas 599 UBC 0 : ereport(ERROR,
600 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
601 : errmsg("cannot access temporary indexes of other sessions")));
602 :
603 : /* Get the information we need from the metapage. */
2256 rhaas 604 CBC 2 : memset(&stats, 0, sizeof(stats));
605 2 : metabuf = _hash_getbuf(rel, HASH_METAPAGE, HASH_READ, LH_META_PAGE);
606 2 : metap = HashPageGetMeta(BufferGetPage(metabuf));
607 2 : stats.version = metap->hashm_version;
608 2 : stats.space_per_page = metap->hashm_bsize;
609 2 : _hash_relbuf(rel, metabuf);
610 :
611 : /* Get the current relation length */
612 2 : nblocks = RelationGetNumberOfBlocks(rel);
613 :
614 : /* prepare access strategy for this index */
615 2 : bstrategy = GetAccessStrategy(BAS_BULKREAD);
616 :
617 : /* Start from blkno 1 as 0th block is metapage */
618 16 : for (blkno = 1; blkno < nblocks; blkno++)
619 : {
620 : Buffer buf;
621 : Page page;
622 :
623 14 : CHECK_FOR_INTERRUPTS();
624 :
625 14 : buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL,
626 : bstrategy);
627 14 : LockBuffer(buf, BUFFER_LOCK_SHARE);
628 14 : page = (Page) BufferGetPage(buf);
629 :
630 14 : if (PageIsNew(page))
2188 rhaas 631 UBC 0 : stats.unused_pages++;
2256 rhaas 632 CBC 14 : else if (PageGetSpecialSize(page) !=
633 : MAXALIGN(sizeof(HashPageOpaqueData)))
2256 rhaas 634 UBC 0 : ereport(ERROR,
635 : (errcode(ERRCODE_INDEX_CORRUPTED),
636 : errmsg("index \"%s\" contains corrupted page at block %u",
637 : RelationGetRelationName(rel),
638 : BufferGetBlockNumber(buf))));
639 : else
640 : {
641 : HashPageOpaque opaque;
642 : int pagetype;
643 :
373 michael 644 CBC 14 : opaque = HashPageGetOpaque(page);
2188 rhaas 645 14 : pagetype = opaque->hasho_flag & LH_PAGE_TYPE;
646 :
647 14 : if (pagetype == LH_BUCKET_PAGE)
648 : {
2256 649 12 : stats.bucket_pages++;
650 12 : GetHashPageStats(page, &stats);
651 : }
2188 652 2 : else if (pagetype == LH_OVERFLOW_PAGE)
653 : {
2256 rhaas 654 UBC 0 : stats.overflow_pages++;
655 0 : GetHashPageStats(page, &stats);
656 : }
2188 rhaas 657 CBC 2 : else if (pagetype == LH_BITMAP_PAGE)
2256 658 2 : stats.bitmap_pages++;
2188 rhaas 659 UBC 0 : else if (pagetype == LH_UNUSED_PAGE)
660 0 : stats.unused_pages++;
661 : else
2256 662 0 : ereport(ERROR,
663 : (errcode(ERRCODE_INDEX_CORRUPTED),
664 : errmsg("unexpected page type 0x%04X in HASH index \"%s\" block %u",
665 : opaque->hasho_flag, RelationGetRelationName(rel),
666 : BufferGetBlockNumber(buf))));
667 : }
2256 rhaas 668 CBC 14 : UnlockReleaseBuffer(buf);
669 : }
670 :
671 : /* Done accessing the index */
672 2 : index_close(rel, AccessShareLock);
673 :
674 : /* Count unused pages as free space. */
2068 675 2 : stats.free_space += (uint64) stats.unused_pages * stats.space_per_page;
676 :
677 : /*
678 : * Total space available for tuples excludes the metapage and the bitmap
679 : * pages.
680 : */
681 2 : total_space = (uint64) (nblocks - (stats.bitmap_pages + 1)) *
682 2 : stats.space_per_page;
683 :
2256 684 2 : if (total_space == 0)
2256 rhaas 685 UBC 0 : free_percent = 0.0;
686 : else
2256 rhaas 687 CBC 2 : free_percent = 100.0 * stats.free_space / total_space;
688 :
689 : /*
690 : * Build a tuple descriptor for our result type
691 : */
692 2 : if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
2256 rhaas 693 UBC 0 : elog(ERROR, "return type must be a row type");
694 :
2256 rhaas 695 CBC 2 : tupleDesc = BlessTupleDesc(tupleDesc);
696 :
697 : /*
698 : * Build and return the tuple
699 : */
700 2 : values[0] = Int32GetDatum(stats.version);
701 2 : values[1] = Int64GetDatum((int64) stats.bucket_pages);
702 2 : values[2] = Int64GetDatum((int64) stats.overflow_pages);
703 2 : values[3] = Int64GetDatum((int64) stats.bitmap_pages);
2188 704 2 : values[4] = Int64GetDatum((int64) stats.unused_pages);
2256 705 2 : values[5] = Int64GetDatum(stats.live_items);
706 2 : values[6] = Int64GetDatum(stats.dead_items);
707 2 : values[7] = Float8GetDatum(free_percent);
2256 rhaas 708 GIC 2 : tuple = heap_form_tuple(tupleDesc, values, nulls);
2256 rhaas 709 ECB :
2256 rhaas 710 GIC 2 : PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
711 : }
712 :
713 : /* -------------------------------------------------
714 : * GetHashPageStats()
715 : *
716 : * Collect statistics of single hash page
717 : * -------------------------------------------------
718 : */
2256 rhaas 719 ECB : static void
2256 rhaas 720 GIC 12 : GetHashPageStats(Page page, HashIndexStat *stats)
2256 rhaas 721 ECB : {
2256 rhaas 722 GIC 12 : OffsetNumber maxoff = PageGetMaxOffsetNumber(page);
723 : int off;
724 :
2256 rhaas 725 ECB : /* count live and dead tuples, and free space */
2256 rhaas 726 GIC 12 : for (off = FirstOffsetNumber; off <= maxoff; off++)
2256 rhaas 727 EUB : {
2153 bruce 728 UIC 0 : ItemId id = PageGetItemId(page, off);
2256 rhaas 729 EUB :
2256 rhaas 730 UBC 0 : if (!ItemIdIsDead(id))
2256 rhaas 731 UIC 0 : stats->live_items++;
2256 rhaas 732 EUB : else
2256 rhaas 733 UIC 0 : stats->dead_items++;
2256 rhaas 734 ECB : }
2256 rhaas 735 CBC 12 : stats->free_space += PageGetExactFreeSpace(page);
2256 rhaas 736 GIC 12 : }
|