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 : */
55 2 : PG_FUNCTION_INFO_V1(pgstatindex);
56 2 : PG_FUNCTION_INFO_V1(pgstatindexbyid);
57 2 : PG_FUNCTION_INFO_V1(pg_relpages);
58 2 : PG_FUNCTION_INFO_V1(pg_relpagesbyid);
59 2 : PG_FUNCTION_INFO_V1(pgstatginindex);
60 4 : PG_FUNCTION_INFO_V1(pgstathashindex);
61 :
62 4 : PG_FUNCTION_INFO_V1(pgstatindex_v1_5);
63 4 : PG_FUNCTION_INFO_V1(pgstatindexbyid_v1_5);
64 4 : PG_FUNCTION_INFO_V1(pg_relpages_v1_5);
65 4 : PG_FUNCTION_INFO_V1(pg_relpagesbyid_v1_5);
66 4 : 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
145 0 : pgstatindex(PG_FUNCTION_ARGS)
146 : {
147 0 : text *relname = PG_GETARG_TEXT_PP(0);
148 : Relation rel;
149 : RangeVar *relrv;
150 :
151 0 : if (!superuser())
152 0 : ereport(ERROR,
153 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
154 : errmsg("must be superuser to use pgstattuple functions")));
155 :
156 0 : relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
157 0 : rel = relation_openrv(relrv, AccessShareLock);
158 :
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
170 20 : pgstatindex_v1_5(PG_FUNCTION_ARGS)
171 : {
172 20 : text *relname = PG_GETARG_TEXT_PP(0);
173 : Relation rel;
174 : RangeVar *relrv;
175 :
176 20 : relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
177 20 : rel = relation_openrv(relrv, AccessShareLock);
178 :
179 20 : 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
188 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
205 2 : pgstatindexbyid_v1_5(PG_FUNCTION_ARGS)
206 : {
207 2 : Oid relid = PG_GETARG_OID(0);
208 : Relation rel;
209 :
210 2 : rel = relation_open(relid, AccessShareLock);
211 :
212 2 : PG_RETURN_DATUM(pgstatindex_impl(rel, fcinfo));
213 : }
214 :
215 : static Datum
216 22 : pgstatindex_impl(Relation rel, FunctionCallInfo fcinfo)
217 : {
218 : Datum result;
219 : BlockNumber nblocks;
220 : BlockNumber blkno;
221 : BTIndexStat indexStat;
222 22 : BufferAccessStrategy bstrategy = GetAccessStrategy(BAS_BULKREAD);
223 :
224 22 : if (!IS_INDEX(rel) || !IS_BTREE(rel))
225 12 : 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 : */
235 10 : if (RELATION_IS_OTHER_TEMP(rel))
236 0 : ereport(ERROR,
237 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
238 : errmsg("cannot access temporary tables of other sessions")));
239 :
240 : /*
241 : * A !indisready index could lead to ERRCODE_DATA_CORRUPTED later, so exit
242 : * early. We're capable of assessing an indisready&&!indisvalid index,
243 : * but the results could be confusing. For example, the index's size
244 : * could be too low for a valid index of the table.
245 : */
246 10 : if (!rel->rd_index->indisvalid)
247 0 : ereport(ERROR,
248 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
249 : errmsg("index \"%s\" is not valid",
250 : RelationGetRelationName(rel))));
251 :
252 : /*
253 : * Read metapage
254 : */
255 : {
256 10 : Buffer buffer = ReadBufferExtended(rel, MAIN_FORKNUM, 0, RBM_NORMAL, bstrategy);
257 10 : Page page = BufferGetPage(buffer);
258 10 : BTMetaPageData *metad = BTPageGetMeta(page);
259 :
260 10 : indexStat.version = metad->btm_version;
261 10 : indexStat.level = metad->btm_level;
262 10 : indexStat.root_blkno = metad->btm_root;
263 :
264 10 : ReleaseBuffer(buffer);
265 : }
266 :
267 : /* -- init counters -- */
268 10 : indexStat.internal_pages = 0;
269 10 : indexStat.leaf_pages = 0;
270 10 : indexStat.empty_pages = 0;
271 10 : indexStat.deleted_pages = 0;
272 :
273 10 : indexStat.max_avail = 0;
274 10 : indexStat.free_space = 0;
275 :
276 10 : indexStat.fragments = 0;
277 :
278 : /*
279 : * Scan all blocks except the metapage
280 : */
281 10 : nblocks = RelationGetNumberOfBlocks(rel);
282 :
283 10 : for (blkno = 1; blkno < nblocks; blkno++)
284 : {
285 : Buffer buffer;
286 : Page page;
287 : BTPageOpaque opaque;
288 :
289 0 : CHECK_FOR_INTERRUPTS();
290 :
291 : /* Read and lock buffer */
292 0 : buffer = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, bstrategy);
293 0 : LockBuffer(buffer, BUFFER_LOCK_SHARE);
294 :
295 0 : page = BufferGetPage(buffer);
296 0 : opaque = BTPageGetOpaque(page);
297 :
298 : /*
299 : * Determine page type, and update totals.
300 : *
301 : * Note that we arbitrarily bucket deleted pages together without
302 : * considering if they're leaf pages or internal pages.
303 : */
304 0 : if (P_ISDELETED(opaque))
305 0 : indexStat.deleted_pages++;
306 0 : else if (P_IGNORE(opaque))
307 0 : indexStat.empty_pages++; /* this is the "half dead" state */
308 0 : else if (P_ISLEAF(opaque))
309 : {
310 : int max_avail;
311 :
312 0 : max_avail = BLCKSZ - (BLCKSZ - ((PageHeader) page)->pd_special + SizeOfPageHeaderData);
313 0 : indexStat.max_avail += max_avail;
314 0 : indexStat.free_space += PageGetFreeSpace(page);
315 :
316 0 : indexStat.leaf_pages++;
317 :
318 : /*
319 : * If the next leaf is on an earlier block, it means a
320 : * fragmentation.
321 : */
322 0 : if (opaque->btpo_next != P_NONE && opaque->btpo_next < blkno)
323 0 : indexStat.fragments++;
324 : }
325 : else
326 0 : indexStat.internal_pages++;
327 :
328 : /* Unlock and release buffer */
329 0 : LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
330 0 : ReleaseBuffer(buffer);
331 : }
332 :
333 10 : relation_close(rel, AccessShareLock);
334 :
335 : /*----------------------------
336 : * Build a result tuple
337 : *----------------------------
338 : */
339 : {
340 : TupleDesc tupleDesc;
341 : int j;
342 : char *values[10];
343 : HeapTuple tuple;
344 :
345 : /* Build a tuple descriptor for our result type */
346 10 : if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
347 0 : elog(ERROR, "return type must be a row type");
348 :
349 10 : j = 0;
350 10 : values[j++] = psprintf("%d", indexStat.version);
351 10 : values[j++] = psprintf("%d", indexStat.level);
352 20 : values[j++] = psprintf(INT64_FORMAT,
353 : (1 + /* include the metapage in index_size */
354 10 : indexStat.leaf_pages +
355 10 : indexStat.internal_pages +
356 10 : indexStat.deleted_pages +
357 10 : indexStat.empty_pages) * BLCKSZ);
358 10 : values[j++] = psprintf("%u", indexStat.root_blkno);
359 10 : values[j++] = psprintf(INT64_FORMAT, indexStat.internal_pages);
360 10 : values[j++] = psprintf(INT64_FORMAT, indexStat.leaf_pages);
361 10 : values[j++] = psprintf(INT64_FORMAT, indexStat.empty_pages);
362 10 : values[j++] = psprintf(INT64_FORMAT, indexStat.deleted_pages);
363 10 : if (indexStat.max_avail > 0)
364 0 : values[j++] = psprintf("%.2f",
365 0 : 100.0 - (double) indexStat.free_space / (double) indexStat.max_avail * 100.0);
366 : else
367 10 : values[j++] = pstrdup("NaN");
368 10 : if (indexStat.leaf_pages > 0)
369 0 : values[j++] = psprintf("%.2f",
370 0 : (double) indexStat.fragments / (double) indexStat.leaf_pages * 100.0);
371 : else
372 10 : values[j++] = pstrdup("NaN");
373 :
374 10 : tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc),
375 : values);
376 :
377 10 : result = HeapTupleGetDatum(tuple);
378 : }
379 :
380 10 : return result;
381 : }
382 :
383 : /* --------------------------------------------------------
384 : * pg_relpages()
385 : *
386 : * Get the number of pages of the table/index.
387 : *
388 : * Usage: SELECT pg_relpages('t1');
389 : * SELECT pg_relpages('t1_pkey');
390 : *
391 : * Must keep superuser() check, see above.
392 : * --------------------------------------------------------
393 : */
394 : Datum
395 0 : pg_relpages(PG_FUNCTION_ARGS)
396 : {
397 0 : text *relname = PG_GETARG_TEXT_PP(0);
398 : Relation rel;
399 : RangeVar *relrv;
400 :
401 0 : if (!superuser())
402 0 : ereport(ERROR,
403 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
404 : errmsg("must be superuser to use pgstattuple functions")));
405 :
406 0 : relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
407 0 : rel = relation_openrv(relrv, AccessShareLock);
408 :
409 0 : PG_RETURN_INT64(pg_relpages_impl(rel));
410 : }
411 :
412 : /* No need for superuser checks in v1.5, see above */
413 : Datum
414 18 : pg_relpages_v1_5(PG_FUNCTION_ARGS)
415 : {
416 18 : text *relname = PG_GETARG_TEXT_PP(0);
417 : Relation rel;
418 : RangeVar *relrv;
419 :
420 18 : relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
421 18 : rel = relation_openrv(relrv, AccessShareLock);
422 :
423 18 : PG_RETURN_INT64(pg_relpages_impl(rel));
424 : }
425 :
426 : /* Must keep superuser() check, see above. */
427 : Datum
428 0 : pg_relpagesbyid(PG_FUNCTION_ARGS)
429 : {
430 0 : Oid relid = PG_GETARG_OID(0);
431 : Relation rel;
432 :
433 0 : if (!superuser())
434 0 : ereport(ERROR,
435 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
436 : errmsg("must be superuser to use pgstattuple functions")));
437 :
438 0 : rel = relation_open(relid, AccessShareLock);
439 :
440 0 : PG_RETURN_INT64(pg_relpages_impl(rel));
441 : }
442 :
443 : /* No need for superuser checks in v1.5, see above */
444 : Datum
445 6 : pg_relpagesbyid_v1_5(PG_FUNCTION_ARGS)
446 : {
447 6 : Oid relid = PG_GETARG_OID(0);
448 : Relation rel;
449 :
450 6 : rel = relation_open(relid, AccessShareLock);
451 :
452 6 : PG_RETURN_INT64(pg_relpages_impl(rel));
453 : }
454 :
455 : static int64
456 24 : pg_relpages_impl(Relation rel)
457 : {
458 : int64 relpages;
459 :
460 24 : if (!RELKIND_HAS_STORAGE(rel->rd_rel->relkind))
461 6 : ereport(ERROR,
462 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
463 : errmsg("cannot get page count of relation \"%s\"",
464 : RelationGetRelationName(rel)),
465 : errdetail_relkind_not_supported(rel->rd_rel->relkind)));
466 :
467 : /* note: this will work OK on non-local temp tables */
468 :
469 18 : relpages = RelationGetNumberOfBlocks(rel);
470 :
471 18 : relation_close(rel, AccessShareLock);
472 :
473 18 : return relpages;
474 : }
475 :
476 : /* ------------------------------------------------------
477 : * pgstatginindex()
478 : *
479 : * Usage: SELECT * FROM pgstatginindex('ginindex');
480 : *
481 : * Must keep superuser() check, see above.
482 : * ------------------------------------------------------
483 : */
484 : Datum
485 0 : pgstatginindex(PG_FUNCTION_ARGS)
486 : {
487 0 : Oid relid = PG_GETARG_OID(0);
488 :
489 0 : if (!superuser())
490 0 : ereport(ERROR,
491 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
492 : errmsg("must be superuser to use pgstattuple functions")));
493 :
494 0 : PG_RETURN_DATUM(pgstatginindex_internal(relid, fcinfo));
495 : }
496 :
497 : /* No need for superuser checks in v1.5, see above */
498 : Datum
499 14 : pgstatginindex_v1_5(PG_FUNCTION_ARGS)
500 : {
501 14 : Oid relid = PG_GETARG_OID(0);
502 :
503 14 : PG_RETURN_DATUM(pgstatginindex_internal(relid, fcinfo));
504 : }
505 :
506 : Datum
507 14 : pgstatginindex_internal(Oid relid, FunctionCallInfo fcinfo)
508 : {
509 : Relation rel;
510 : Buffer buffer;
511 : Page page;
512 : GinMetaPageData *metadata;
513 : GinIndexStat stats;
514 : HeapTuple tuple;
515 : TupleDesc tupleDesc;
516 : Datum values[3];
517 14 : bool nulls[3] = {false, false, false};
518 : Datum result;
519 :
520 14 : rel = relation_open(relid, AccessShareLock);
521 :
522 14 : if (!IS_INDEX(rel) || !IS_GIN(rel))
523 12 : ereport(ERROR,
524 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
525 : errmsg("relation \"%s\" is not a GIN index",
526 : RelationGetRelationName(rel))));
527 :
528 : /*
529 : * Reject attempts to read non-local temporary relations; we would be
530 : * likely to get wrong data since we have no visibility into the owning
531 : * session's local buffers.
532 : */
533 2 : if (RELATION_IS_OTHER_TEMP(rel))
534 0 : ereport(ERROR,
535 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
536 : errmsg("cannot access temporary indexes of other sessions")));
537 :
538 : /* see pgstatindex_impl */
539 2 : if (!rel->rd_index->indisvalid)
540 0 : ereport(ERROR,
541 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
542 : errmsg("index \"%s\" is not valid",
543 : RelationGetRelationName(rel))));
544 :
545 : /*
546 : * Read metapage
547 : */
548 2 : buffer = ReadBuffer(rel, GIN_METAPAGE_BLKNO);
549 2 : LockBuffer(buffer, GIN_SHARE);
550 2 : page = BufferGetPage(buffer);
551 2 : metadata = GinPageGetMeta(page);
552 :
553 2 : stats.version = metadata->ginVersion;
554 2 : stats.pending_pages = metadata->nPendingPages;
555 2 : stats.pending_tuples = metadata->nPendingHeapTuples;
556 :
557 2 : UnlockReleaseBuffer(buffer);
558 2 : relation_close(rel, AccessShareLock);
559 :
560 : /*
561 : * Build a tuple descriptor for our result type
562 : */
563 2 : if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
564 0 : elog(ERROR, "return type must be a row type");
565 :
566 2 : values[0] = Int32GetDatum(stats.version);
567 2 : values[1] = UInt32GetDatum(stats.pending_pages);
568 2 : values[2] = Int64GetDatum(stats.pending_tuples);
569 :
570 : /*
571 : * Build and return the tuple
572 : */
573 2 : tuple = heap_form_tuple(tupleDesc, values, nulls);
574 2 : result = HeapTupleGetDatum(tuple);
575 :
576 2 : return result;
577 : }
578 :
579 : /* ------------------------------------------------------
580 : * pgstathashindex()
581 : *
582 : * Usage: SELECT * FROM pgstathashindex('hashindex');
583 : * ------------------------------------------------------
584 : */
585 : Datum
586 18 : pgstathashindex(PG_FUNCTION_ARGS)
587 : {
588 18 : Oid relid = PG_GETARG_OID(0);
589 : BlockNumber nblocks;
590 : BlockNumber blkno;
591 : Relation rel;
592 : HashIndexStat stats;
593 : BufferAccessStrategy bstrategy;
594 : HeapTuple tuple;
595 : TupleDesc tupleDesc;
596 : Datum values[8];
597 18 : bool nulls[8] = {0};
598 : Buffer metabuf;
599 : HashMetaPage metap;
600 : float8 free_percent;
601 : uint64 total_space;
602 :
603 18 : rel = relation_open(relid, AccessShareLock);
604 :
605 18 : if (!IS_INDEX(rel) || !IS_HASH(rel))
606 14 : ereport(ERROR,
607 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
608 : errmsg("relation \"%s\" is not a hash index",
609 : RelationGetRelationName(rel))));
610 :
611 : /*
612 : * Reject attempts to read non-local temporary relations; we would be
613 : * likely to get wrong data since we have no visibility into the owning
614 : * session's local buffers.
615 : */
616 4 : if (RELATION_IS_OTHER_TEMP(rel))
617 0 : ereport(ERROR,
618 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
619 : errmsg("cannot access temporary indexes of other sessions")));
620 :
621 : /* see pgstatindex_impl */
622 4 : if (!rel->rd_index->indisvalid)
623 0 : ereport(ERROR,
624 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
625 : errmsg("index \"%s\" is not valid",
626 : RelationGetRelationName(rel))));
627 :
628 : /* Get the information we need from the metapage. */
629 4 : memset(&stats, 0, sizeof(stats));
630 4 : metabuf = _hash_getbuf(rel, HASH_METAPAGE, HASH_READ, LH_META_PAGE);
631 4 : metap = HashPageGetMeta(BufferGetPage(metabuf));
632 4 : stats.version = metap->hashm_version;
633 4 : stats.space_per_page = metap->hashm_bsize;
634 4 : _hash_relbuf(rel, metabuf);
635 :
636 : /* Get the current relation length */
637 4 : nblocks = RelationGetNumberOfBlocks(rel);
638 :
639 : /* prepare access strategy for this index */
640 4 : bstrategy = GetAccessStrategy(BAS_BULKREAD);
641 :
642 : /* Start from blkno 1 as 0th block is metapage */
643 32 : for (blkno = 1; blkno < nblocks; blkno++)
644 : {
645 : Buffer buf;
646 : Page page;
647 :
648 28 : CHECK_FOR_INTERRUPTS();
649 :
650 28 : buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL,
651 : bstrategy);
652 28 : LockBuffer(buf, BUFFER_LOCK_SHARE);
653 28 : page = (Page) BufferGetPage(buf);
654 :
655 28 : if (PageIsNew(page))
656 0 : stats.unused_pages++;
657 28 : else if (PageGetSpecialSize(page) !=
658 : MAXALIGN(sizeof(HashPageOpaqueData)))
659 0 : ereport(ERROR,
660 : (errcode(ERRCODE_INDEX_CORRUPTED),
661 : errmsg("index \"%s\" contains corrupted page at block %u",
662 : RelationGetRelationName(rel),
663 : BufferGetBlockNumber(buf))));
664 : else
665 : {
666 : HashPageOpaque opaque;
667 : int pagetype;
668 :
669 28 : opaque = HashPageGetOpaque(page);
670 28 : pagetype = opaque->hasho_flag & LH_PAGE_TYPE;
671 :
672 28 : if (pagetype == LH_BUCKET_PAGE)
673 : {
674 24 : stats.bucket_pages++;
675 24 : GetHashPageStats(page, &stats);
676 : }
677 4 : else if (pagetype == LH_OVERFLOW_PAGE)
678 : {
679 0 : stats.overflow_pages++;
680 0 : GetHashPageStats(page, &stats);
681 : }
682 4 : else if (pagetype == LH_BITMAP_PAGE)
683 4 : stats.bitmap_pages++;
684 0 : else if (pagetype == LH_UNUSED_PAGE)
685 0 : stats.unused_pages++;
686 : else
687 0 : ereport(ERROR,
688 : (errcode(ERRCODE_INDEX_CORRUPTED),
689 : errmsg("unexpected page type 0x%04X in HASH index \"%s\" block %u",
690 : opaque->hasho_flag, RelationGetRelationName(rel),
691 : BufferGetBlockNumber(buf))));
692 : }
693 28 : UnlockReleaseBuffer(buf);
694 : }
695 :
696 : /* Done accessing the index */
697 4 : index_close(rel, AccessShareLock);
698 :
699 : /* Count unused pages as free space. */
700 4 : stats.free_space += (uint64) stats.unused_pages * stats.space_per_page;
701 :
702 : /*
703 : * Total space available for tuples excludes the metapage and the bitmap
704 : * pages.
705 : */
706 4 : total_space = (uint64) (nblocks - (stats.bitmap_pages + 1)) *
707 4 : stats.space_per_page;
708 :
709 4 : if (total_space == 0)
710 0 : free_percent = 0.0;
711 : else
712 4 : free_percent = 100.0 * stats.free_space / total_space;
713 :
714 : /*
715 : * Build a tuple descriptor for our result type
716 : */
717 4 : if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
718 0 : elog(ERROR, "return type must be a row type");
719 :
720 4 : tupleDesc = BlessTupleDesc(tupleDesc);
721 :
722 : /*
723 : * Build and return the tuple
724 : */
725 4 : values[0] = Int32GetDatum(stats.version);
726 4 : values[1] = Int64GetDatum((int64) stats.bucket_pages);
727 4 : values[2] = Int64GetDatum((int64) stats.overflow_pages);
728 4 : values[3] = Int64GetDatum((int64) stats.bitmap_pages);
729 4 : values[4] = Int64GetDatum((int64) stats.unused_pages);
730 4 : values[5] = Int64GetDatum(stats.live_items);
731 4 : values[6] = Int64GetDatum(stats.dead_items);
732 4 : values[7] = Float8GetDatum(free_percent);
733 4 : tuple = heap_form_tuple(tupleDesc, values, nulls);
734 :
735 4 : PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
736 : }
737 :
738 : /* -------------------------------------------------
739 : * GetHashPageStats()
740 : *
741 : * Collect statistics of single hash page
742 : * -------------------------------------------------
743 : */
744 : static void
745 24 : GetHashPageStats(Page page, HashIndexStat *stats)
746 : {
747 24 : OffsetNumber maxoff = PageGetMaxOffsetNumber(page);
748 : int off;
749 :
750 : /* count live and dead tuples, and free space */
751 24 : for (off = FirstOffsetNumber; off <= maxoff; off++)
752 : {
753 0 : ItemId id = PageGetItemId(page, off);
754 :
755 0 : if (!ItemIdIsDead(id))
756 0 : stats->live_items++;
757 : else
758 0 : stats->dead_items++;
759 : }
760 24 : stats->free_space += PageGetExactFreeSpace(page);
761 24 : }
|