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 "catalog/namespace.h"
36 : #include "catalog/pg_am.h"
37 : #include "funcapi.h"
38 : #include "miscadmin.h"
39 : #include "storage/bufmgr.h"
40 : #include "storage/read_stream.h"
41 : #include "utils/rel.h"
42 : #include "utils/varlena.h"
43 :
44 :
45 : /*
46 : * Because of backward-compatibility issue, we have decided to have
47 : * two types of interfaces, with regclass-type input arg and text-type
48 : * input arg, for each function.
49 : *
50 : * Those functions which have text-type input arg will be deprecated
51 : * in the future release.
52 : */
53 1 : PG_FUNCTION_INFO_V1(pgstatindex);
54 1 : PG_FUNCTION_INFO_V1(pgstatindexbyid);
55 1 : PG_FUNCTION_INFO_V1(pg_relpages);
56 1 : PG_FUNCTION_INFO_V1(pg_relpagesbyid);
57 1 : PG_FUNCTION_INFO_V1(pgstatginindex);
58 2 : PG_FUNCTION_INFO_V1(pgstathashindex);
59 :
60 2 : PG_FUNCTION_INFO_V1(pgstatindex_v1_5);
61 2 : PG_FUNCTION_INFO_V1(pgstatindexbyid_v1_5);
62 2 : PG_FUNCTION_INFO_V1(pg_relpages_v1_5);
63 2 : PG_FUNCTION_INFO_V1(pg_relpagesbyid_v1_5);
64 2 : PG_FUNCTION_INFO_V1(pgstatginindex_v1_5);
65 :
66 : Datum pgstatginindex_internal(Oid relid, FunctionCallInfo fcinfo);
67 :
68 : #define IS_INDEX(r) ((r)->rd_rel->relkind == RELKIND_INDEX)
69 : #define IS_BTREE(r) ((r)->rd_rel->relam == BTREE_AM_OID)
70 : #define IS_GIN(r) ((r)->rd_rel->relam == GIN_AM_OID)
71 : #define IS_HASH(r) ((r)->rd_rel->relam == HASH_AM_OID)
72 :
73 : /* ------------------------------------------------
74 : * A structure for a whole btree index statistics
75 : * used by pgstatindex().
76 : * ------------------------------------------------
77 : */
78 : typedef struct BTIndexStat
79 : {
80 : uint32 version;
81 : uint32 level;
82 : BlockNumber root_blkno;
83 :
84 : uint64 internal_pages;
85 : uint64 leaf_pages;
86 : uint64 empty_pages;
87 : uint64 deleted_pages;
88 :
89 : uint64 max_avail;
90 : uint64 free_space;
91 :
92 : uint64 fragments;
93 : } BTIndexStat;
94 :
95 : /* ------------------------------------------------
96 : * A structure for a whole GIN index statistics
97 : * used by pgstatginindex().
98 : * ------------------------------------------------
99 : */
100 : typedef struct GinIndexStat
101 : {
102 : int32 version;
103 :
104 : BlockNumber pending_pages;
105 : int64 pending_tuples;
106 : } GinIndexStat;
107 :
108 : /* ------------------------------------------------
109 : * A structure for a whole HASH index statistics
110 : * used by pgstathashindex().
111 : * ------------------------------------------------
112 : */
113 : typedef struct HashIndexStat
114 : {
115 : int32 version;
116 : int32 space_per_page;
117 :
118 : BlockNumber bucket_pages;
119 : BlockNumber overflow_pages;
120 : BlockNumber bitmap_pages;
121 : BlockNumber unused_pages;
122 :
123 : int64 live_items;
124 : int64 dead_items;
125 : uint64 free_space;
126 : } HashIndexStat;
127 :
128 : static Datum pgstatindex_impl(Relation rel, FunctionCallInfo fcinfo);
129 : static int64 pg_relpages_impl(Relation rel);
130 : static void GetHashPageStats(Page page, HashIndexStat *stats);
131 :
132 : /* ------------------------------------------------------
133 : * pgstatindex()
134 : *
135 : * Usage: SELECT * FROM pgstatindex('t1_pkey');
136 : *
137 : * The superuser() check here must be kept as the library might be upgraded
138 : * without the extension being upgraded, meaning that in pre-1.5 installations
139 : * these functions could be called by any user.
140 : * ------------------------------------------------------
141 : */
142 : Datum
143 0 : pgstatindex(PG_FUNCTION_ARGS)
144 : {
145 0 : text *relname = PG_GETARG_TEXT_PP(0);
146 : Relation rel;
147 : RangeVar *relrv;
148 :
149 0 : if (!superuser())
150 0 : ereport(ERROR,
151 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
152 : errmsg("must be superuser to use pgstattuple functions")));
153 :
154 0 : relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
155 0 : rel = relation_openrv(relrv, AccessShareLock);
156 :
157 0 : PG_RETURN_DATUM(pgstatindex_impl(rel, fcinfo));
158 : }
159 :
160 : /*
161 : * As of pgstattuple version 1.5, we no longer need to check if the user
162 : * is a superuser because we REVOKE EXECUTE on the function from PUBLIC.
163 : * Users can then grant access to it based on their policies.
164 : *
165 : * Otherwise identical to pgstatindex (above).
166 : */
167 : Datum
168 11 : pgstatindex_v1_5(PG_FUNCTION_ARGS)
169 : {
170 11 : text *relname = PG_GETARG_TEXT_PP(0);
171 : Relation rel;
172 : RangeVar *relrv;
173 :
174 11 : relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
175 11 : rel = relation_openrv(relrv, AccessShareLock);
176 :
177 11 : PG_RETURN_DATUM(pgstatindex_impl(rel, fcinfo));
178 : }
179 :
180 : /*
181 : * The superuser() check here must be kept as the library might be upgraded
182 : * without the extension being upgraded, meaning that in pre-1.5 installations
183 : * these functions could be called by any user.
184 : */
185 : Datum
186 0 : pgstatindexbyid(PG_FUNCTION_ARGS)
187 : {
188 0 : Oid relid = PG_GETARG_OID(0);
189 : Relation rel;
190 :
191 0 : if (!superuser())
192 0 : ereport(ERROR,
193 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
194 : errmsg("must be superuser to use pgstattuple functions")));
195 :
196 0 : rel = relation_open(relid, AccessShareLock);
197 :
198 0 : PG_RETURN_DATUM(pgstatindex_impl(rel, fcinfo));
199 : }
200 :
201 : /* No need for superuser checks in v1.5, see above */
202 : Datum
203 1 : pgstatindexbyid_v1_5(PG_FUNCTION_ARGS)
204 : {
205 1 : Oid relid = PG_GETARG_OID(0);
206 : Relation rel;
207 :
208 1 : rel = relation_open(relid, AccessShareLock);
209 :
210 1 : PG_RETURN_DATUM(pgstatindex_impl(rel, fcinfo));
211 : }
212 :
213 : static Datum
214 12 : pgstatindex_impl(Relation rel, FunctionCallInfo fcinfo)
215 : {
216 : Datum result;
217 : BlockNumber nblocks;
218 : BlockNumber blkno;
219 : BTIndexStat indexStat;
220 12 : BufferAccessStrategy bstrategy = GetAccessStrategy(BAS_BULKREAD);
221 : BlockRangeReadStreamPrivate p;
222 : ReadStream *stream;
223 : BlockNumber startblk;
224 :
225 12 : if (!IS_INDEX(rel) || !IS_BTREE(rel))
226 7 : ereport(ERROR,
227 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
228 : errmsg("relation \"%s\" is not a btree index",
229 : RelationGetRelationName(rel))));
230 :
231 : /*
232 : * Reject attempts to read non-local temporary relations; we would be
233 : * likely to get wrong data since we have no visibility into the owning
234 : * session's local buffers.
235 : */
236 5 : if (RELATION_IS_OTHER_TEMP(rel))
237 0 : ereport(ERROR,
238 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
239 : errmsg("cannot access temporary tables of other sessions")));
240 :
241 : /*
242 : * A !indisready index could lead to ERRCODE_DATA_CORRUPTED later, so exit
243 : * early. We're capable of assessing an indisready&&!indisvalid index,
244 : * but the results could be confusing. For example, the index's size
245 : * could be too low for a valid index of the table.
246 : */
247 5 : if (!rel->rd_index->indisvalid)
248 0 : ereport(ERROR,
249 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
250 : errmsg("index \"%s\" is not valid",
251 : RelationGetRelationName(rel))));
252 :
253 : /*
254 : * Read metapage
255 : */
256 : {
257 5 : Buffer buffer = ReadBufferExtended(rel, MAIN_FORKNUM, 0, RBM_NORMAL, bstrategy);
258 5 : Page page = BufferGetPage(buffer);
259 5 : BTMetaPageData *metad = BTPageGetMeta(page);
260 :
261 5 : indexStat.version = metad->btm_version;
262 5 : indexStat.level = metad->btm_level;
263 5 : indexStat.root_blkno = metad->btm_root;
264 :
265 5 : ReleaseBuffer(buffer);
266 : }
267 :
268 : /* -- init counters -- */
269 5 : indexStat.internal_pages = 0;
270 5 : indexStat.leaf_pages = 0;
271 5 : indexStat.empty_pages = 0;
272 5 : indexStat.deleted_pages = 0;
273 :
274 5 : indexStat.max_avail = 0;
275 5 : indexStat.free_space = 0;
276 :
277 5 : indexStat.fragments = 0;
278 :
279 : /*
280 : * Scan all blocks except the metapage (0th page) using streaming reads
281 : */
282 5 : nblocks = RelationGetNumberOfBlocks(rel);
283 5 : startblk = BTREE_METAPAGE + 1;
284 :
285 5 : p.current_blocknum = startblk;
286 5 : p.last_exclusive = nblocks;
287 :
288 : /*
289 : * It is safe to use batchmode as block_range_read_stream_cb takes no
290 : * locks.
291 : */
292 5 : stream = read_stream_begin_relation(READ_STREAM_FULL |
293 : READ_STREAM_USE_BATCHING,
294 : bstrategy,
295 : rel,
296 : MAIN_FORKNUM,
297 : block_range_read_stream_cb,
298 : &p,
299 : 0);
300 :
301 5 : for (blkno = startblk; blkno < nblocks; blkno++)
302 : {
303 : Buffer buffer;
304 : Page page;
305 : BTPageOpaque opaque;
306 :
307 0 : CHECK_FOR_INTERRUPTS();
308 :
309 0 : buffer = read_stream_next_buffer(stream, NULL);
310 0 : LockBuffer(buffer, BUFFER_LOCK_SHARE);
311 :
312 0 : page = BufferGetPage(buffer);
313 0 : opaque = BTPageGetOpaque(page);
314 :
315 : /*
316 : * Determine page type, and update totals.
317 : *
318 : * Note that we arbitrarily bucket deleted pages together without
319 : * considering if they're leaf pages or internal pages.
320 : */
321 0 : if (P_ISDELETED(opaque))
322 0 : indexStat.deleted_pages++;
323 0 : else if (P_IGNORE(opaque))
324 0 : indexStat.empty_pages++; /* this is the "half dead" state */
325 0 : else if (P_ISLEAF(opaque))
326 : {
327 : int max_avail;
328 :
329 0 : max_avail = BLCKSZ - (BLCKSZ - ((PageHeader) page)->pd_special + SizeOfPageHeaderData);
330 0 : indexStat.max_avail += max_avail;
331 0 : indexStat.free_space += PageGetExactFreeSpace(page);
332 :
333 0 : indexStat.leaf_pages++;
334 :
335 : /*
336 : * If the next leaf is on an earlier block, it means a
337 : * fragmentation.
338 : */
339 0 : if (opaque->btpo_next != P_NONE && opaque->btpo_next < blkno)
340 0 : indexStat.fragments++;
341 : }
342 : else
343 0 : indexStat.internal_pages++;
344 :
345 0 : UnlockReleaseBuffer(buffer);
346 : }
347 :
348 : Assert(read_stream_next_buffer(stream, NULL) == InvalidBuffer);
349 5 : read_stream_end(stream);
350 :
351 5 : relation_close(rel, AccessShareLock);
352 :
353 : /*----------------------------
354 : * Build a result tuple
355 : *----------------------------
356 : */
357 : {
358 : TupleDesc tupleDesc;
359 : int j;
360 : char *values[10];
361 : HeapTuple tuple;
362 :
363 : /* Build a tuple descriptor for our result type */
364 5 : if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
365 0 : elog(ERROR, "return type must be a row type");
366 :
367 5 : j = 0;
368 5 : values[j++] = psprintf("%d", indexStat.version);
369 5 : values[j++] = psprintf("%d", indexStat.level);
370 10 : values[j++] = psprintf(INT64_FORMAT,
371 : (1 + /* include the metapage in index_size */
372 5 : indexStat.leaf_pages +
373 5 : indexStat.internal_pages +
374 5 : indexStat.deleted_pages +
375 5 : indexStat.empty_pages) * BLCKSZ);
376 5 : values[j++] = psprintf("%u", indexStat.root_blkno);
377 5 : values[j++] = psprintf(INT64_FORMAT, indexStat.internal_pages);
378 5 : values[j++] = psprintf(INT64_FORMAT, indexStat.leaf_pages);
379 5 : values[j++] = psprintf(INT64_FORMAT, indexStat.empty_pages);
380 5 : values[j++] = psprintf(INT64_FORMAT, indexStat.deleted_pages);
381 5 : if (indexStat.max_avail > 0)
382 0 : values[j++] = psprintf("%.2f",
383 0 : 100.0 - (double) indexStat.free_space / (double) indexStat.max_avail * 100.0);
384 : else
385 5 : values[j++] = pstrdup("NaN");
386 5 : if (indexStat.leaf_pages > 0)
387 0 : values[j++] = psprintf("%.2f",
388 0 : (double) indexStat.fragments / (double) indexStat.leaf_pages * 100.0);
389 : else
390 5 : values[j++] = pstrdup("NaN");
391 :
392 5 : tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(tupleDesc),
393 : values);
394 :
395 5 : result = HeapTupleGetDatum(tuple);
396 : }
397 :
398 5 : return result;
399 : }
400 :
401 : /* --------------------------------------------------------
402 : * pg_relpages()
403 : *
404 : * Get the number of pages of the table/index.
405 : *
406 : * Usage: SELECT pg_relpages('t1');
407 : * SELECT pg_relpages('t1_pkey');
408 : *
409 : * Must keep superuser() check, see above.
410 : * --------------------------------------------------------
411 : */
412 : Datum
413 0 : pg_relpages(PG_FUNCTION_ARGS)
414 : {
415 0 : text *relname = PG_GETARG_TEXT_PP(0);
416 : Relation rel;
417 : RangeVar *relrv;
418 :
419 0 : if (!superuser())
420 0 : ereport(ERROR,
421 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
422 : errmsg("must be superuser to use pgstattuple functions")));
423 :
424 0 : relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
425 0 : rel = relation_openrv(relrv, AccessShareLock);
426 :
427 0 : PG_RETURN_INT64(pg_relpages_impl(rel));
428 : }
429 :
430 : /* No need for superuser checks in v1.5, see above */
431 : Datum
432 10 : pg_relpages_v1_5(PG_FUNCTION_ARGS)
433 : {
434 10 : text *relname = PG_GETARG_TEXT_PP(0);
435 : Relation rel;
436 : RangeVar *relrv;
437 :
438 10 : relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
439 10 : rel = relation_openrv(relrv, AccessShareLock);
440 :
441 10 : PG_RETURN_INT64(pg_relpages_impl(rel));
442 : }
443 :
444 : /* Must keep superuser() check, see above. */
445 : Datum
446 0 : pg_relpagesbyid(PG_FUNCTION_ARGS)
447 : {
448 0 : Oid relid = PG_GETARG_OID(0);
449 : Relation rel;
450 :
451 0 : if (!superuser())
452 0 : ereport(ERROR,
453 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
454 : errmsg("must be superuser to use pgstattuple functions")));
455 :
456 0 : rel = relation_open(relid, AccessShareLock);
457 :
458 0 : PG_RETURN_INT64(pg_relpages_impl(rel));
459 : }
460 :
461 : /* No need for superuser checks in v1.5, see above */
462 : Datum
463 3 : pg_relpagesbyid_v1_5(PG_FUNCTION_ARGS)
464 : {
465 3 : Oid relid = PG_GETARG_OID(0);
466 : Relation rel;
467 :
468 3 : rel = relation_open(relid, AccessShareLock);
469 :
470 3 : PG_RETURN_INT64(pg_relpages_impl(rel));
471 : }
472 :
473 : static int64
474 13 : pg_relpages_impl(Relation rel)
475 : {
476 : int64 relpages;
477 :
478 13 : if (!RELKIND_HAS_STORAGE(rel->rd_rel->relkind))
479 3 : ereport(ERROR,
480 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
481 : errmsg("cannot get page count of relation \"%s\"",
482 : RelationGetRelationName(rel)),
483 : errdetail_relkind_not_supported(rel->rd_rel->relkind)));
484 :
485 : /* note: this will work OK on non-local temp tables */
486 :
487 10 : relpages = RelationGetNumberOfBlocks(rel);
488 :
489 10 : relation_close(rel, AccessShareLock);
490 :
491 10 : return relpages;
492 : }
493 :
494 : /* ------------------------------------------------------
495 : * pgstatginindex()
496 : *
497 : * Usage: SELECT * FROM pgstatginindex('ginindex');
498 : *
499 : * Must keep superuser() check, see above.
500 : * ------------------------------------------------------
501 : */
502 : Datum
503 0 : pgstatginindex(PG_FUNCTION_ARGS)
504 : {
505 0 : Oid relid = PG_GETARG_OID(0);
506 :
507 0 : if (!superuser())
508 0 : ereport(ERROR,
509 : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
510 : errmsg("must be superuser to use pgstattuple functions")));
511 :
512 0 : PG_RETURN_DATUM(pgstatginindex_internal(relid, fcinfo));
513 : }
514 :
515 : /* No need for superuser checks in v1.5, see above */
516 : Datum
517 8 : pgstatginindex_v1_5(PG_FUNCTION_ARGS)
518 : {
519 8 : Oid relid = PG_GETARG_OID(0);
520 :
521 8 : PG_RETURN_DATUM(pgstatginindex_internal(relid, fcinfo));
522 : }
523 :
524 : Datum
525 8 : pgstatginindex_internal(Oid relid, FunctionCallInfo fcinfo)
526 : {
527 : Relation rel;
528 : Buffer buffer;
529 : Page page;
530 : GinMetaPageData *metadata;
531 : GinIndexStat stats;
532 : HeapTuple tuple;
533 : TupleDesc tupleDesc;
534 : Datum values[3];
535 8 : bool nulls[3] = {false, false, false};
536 : Datum result;
537 :
538 : /*
539 : * This uses relation_open() and not index_open(). The latter allows
540 : * partitioned indexes, and these are forbidden here.
541 : */
542 8 : rel = relation_open(relid, AccessShareLock);
543 :
544 8 : if (!IS_INDEX(rel) || !IS_GIN(rel))
545 7 : ereport(ERROR,
546 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
547 : errmsg("relation \"%s\" is not a GIN index",
548 : RelationGetRelationName(rel))));
549 :
550 : /*
551 : * Reject attempts to read non-local temporary relations; we would be
552 : * likely to get wrong data since we have no visibility into the owning
553 : * session's local buffers.
554 : */
555 1 : if (RELATION_IS_OTHER_TEMP(rel))
556 0 : ereport(ERROR,
557 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
558 : errmsg("cannot access temporary indexes of other sessions")));
559 :
560 : /* see pgstatindex_impl */
561 1 : if (!rel->rd_index->indisvalid)
562 0 : ereport(ERROR,
563 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
564 : errmsg("index \"%s\" is not valid",
565 : RelationGetRelationName(rel))));
566 :
567 : /*
568 : * Read metapage
569 : */
570 1 : buffer = ReadBuffer(rel, GIN_METAPAGE_BLKNO);
571 1 : LockBuffer(buffer, GIN_SHARE);
572 1 : page = BufferGetPage(buffer);
573 1 : metadata = GinPageGetMeta(page);
574 :
575 1 : stats.version = metadata->ginVersion;
576 1 : stats.pending_pages = metadata->nPendingPages;
577 1 : stats.pending_tuples = metadata->nPendingHeapTuples;
578 :
579 1 : UnlockReleaseBuffer(buffer);
580 1 : relation_close(rel, AccessShareLock);
581 :
582 : /*
583 : * Build a tuple descriptor for our result type
584 : */
585 1 : if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
586 0 : elog(ERROR, "return type must be a row type");
587 :
588 1 : values[0] = Int32GetDatum(stats.version);
589 1 : values[1] = UInt32GetDatum(stats.pending_pages);
590 1 : values[2] = Int64GetDatum(stats.pending_tuples);
591 :
592 : /*
593 : * Build and return the tuple
594 : */
595 1 : tuple = heap_form_tuple(tupleDesc, values, nulls);
596 1 : result = HeapTupleGetDatum(tuple);
597 :
598 1 : return result;
599 : }
600 :
601 : /* ------------------------------------------------------
602 : * pgstathashindex()
603 : *
604 : * Usage: SELECT * FROM pgstathashindex('hashindex');
605 : * ------------------------------------------------------
606 : */
607 : Datum
608 10 : pgstathashindex(PG_FUNCTION_ARGS)
609 : {
610 10 : Oid relid = PG_GETARG_OID(0);
611 : BlockNumber nblocks;
612 : BlockNumber blkno;
613 : Relation rel;
614 : HashIndexStat stats;
615 : BufferAccessStrategy bstrategy;
616 : HeapTuple tuple;
617 : TupleDesc tupleDesc;
618 : Datum values[8];
619 10 : bool nulls[8] = {0};
620 : Buffer metabuf;
621 : HashMetaPage metap;
622 : float8 free_percent;
623 : uint64 total_space;
624 : BlockRangeReadStreamPrivate p;
625 : ReadStream *stream;
626 : BlockNumber startblk;
627 :
628 : /*
629 : * This uses relation_open() and not index_open(). The latter allows
630 : * partitioned indexes, and these are forbidden here.
631 : */
632 10 : rel = relation_open(relid, AccessShareLock);
633 :
634 10 : if (!IS_INDEX(rel) || !IS_HASH(rel))
635 8 : ereport(ERROR,
636 : (errcode(ERRCODE_WRONG_OBJECT_TYPE),
637 : errmsg("relation \"%s\" is not a hash index",
638 : RelationGetRelationName(rel))));
639 :
640 : /*
641 : * Reject attempts to read non-local temporary relations; we would be
642 : * likely to get wrong data since we have no visibility into the owning
643 : * session's local buffers.
644 : */
645 2 : if (RELATION_IS_OTHER_TEMP(rel))
646 0 : ereport(ERROR,
647 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
648 : errmsg("cannot access temporary indexes of other sessions")));
649 :
650 : /* see pgstatindex_impl */
651 2 : if (!rel->rd_index->indisvalid)
652 0 : ereport(ERROR,
653 : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
654 : errmsg("index \"%s\" is not valid",
655 : RelationGetRelationName(rel))));
656 :
657 : /* Get the information we need from the metapage. */
658 2 : memset(&stats, 0, sizeof(stats));
659 2 : metabuf = _hash_getbuf(rel, HASH_METAPAGE, HASH_READ, LH_META_PAGE);
660 2 : metap = HashPageGetMeta(BufferGetPage(metabuf));
661 2 : stats.version = metap->hashm_version;
662 2 : stats.space_per_page = metap->hashm_bsize;
663 2 : _hash_relbuf(rel, metabuf);
664 :
665 : /* Get the current relation length */
666 2 : nblocks = RelationGetNumberOfBlocks(rel);
667 :
668 : /* prepare access strategy for this index */
669 2 : bstrategy = GetAccessStrategy(BAS_BULKREAD);
670 :
671 : /* Scan all blocks except the metapage (0th page) using streaming reads */
672 2 : startblk = HASH_METAPAGE + 1;
673 :
674 2 : p.current_blocknum = startblk;
675 2 : p.last_exclusive = nblocks;
676 :
677 : /*
678 : * It is safe to use batchmode as block_range_read_stream_cb takes no
679 : * locks.
680 : */
681 2 : stream = read_stream_begin_relation(READ_STREAM_FULL |
682 : READ_STREAM_USE_BATCHING,
683 : bstrategy,
684 : rel,
685 : MAIN_FORKNUM,
686 : block_range_read_stream_cb,
687 : &p,
688 : 0);
689 :
690 16 : for (blkno = startblk; blkno < nblocks; blkno++)
691 : {
692 : Buffer buf;
693 : Page page;
694 :
695 14 : CHECK_FOR_INTERRUPTS();
696 :
697 14 : buf = read_stream_next_buffer(stream, NULL);
698 14 : LockBuffer(buf, BUFFER_LOCK_SHARE);
699 14 : page = BufferGetPage(buf);
700 :
701 14 : if (PageIsNew(page))
702 0 : stats.unused_pages++;
703 14 : else if (PageGetSpecialSize(page) !=
704 : MAXALIGN(sizeof(HashPageOpaqueData)))
705 0 : ereport(ERROR,
706 : (errcode(ERRCODE_INDEX_CORRUPTED),
707 : errmsg("index \"%s\" contains corrupted page at block %u",
708 : RelationGetRelationName(rel),
709 : BufferGetBlockNumber(buf))));
710 : else
711 : {
712 : HashPageOpaque opaque;
713 : int pagetype;
714 :
715 14 : opaque = HashPageGetOpaque(page);
716 14 : pagetype = opaque->hasho_flag & LH_PAGE_TYPE;
717 :
718 14 : if (pagetype == LH_BUCKET_PAGE)
719 : {
720 12 : stats.bucket_pages++;
721 12 : GetHashPageStats(page, &stats);
722 : }
723 2 : else if (pagetype == LH_OVERFLOW_PAGE)
724 : {
725 0 : stats.overflow_pages++;
726 0 : GetHashPageStats(page, &stats);
727 : }
728 2 : else if (pagetype == LH_BITMAP_PAGE)
729 2 : stats.bitmap_pages++;
730 0 : else if (pagetype == LH_UNUSED_PAGE)
731 0 : stats.unused_pages++;
732 : else
733 0 : ereport(ERROR,
734 : (errcode(ERRCODE_INDEX_CORRUPTED),
735 : errmsg("unexpected page type 0x%04X in HASH index \"%s\" block %u",
736 : opaque->hasho_flag, RelationGetRelationName(rel),
737 : BufferGetBlockNumber(buf))));
738 : }
739 14 : UnlockReleaseBuffer(buf);
740 : }
741 :
742 : Assert(read_stream_next_buffer(stream, NULL) == InvalidBuffer);
743 2 : read_stream_end(stream);
744 :
745 : /* Done accessing the index */
746 2 : relation_close(rel, AccessShareLock);
747 :
748 : /* Count unused pages as free space. */
749 2 : stats.free_space += (uint64) stats.unused_pages * stats.space_per_page;
750 :
751 : /*
752 : * Total space available for tuples excludes the metapage and the bitmap
753 : * pages.
754 : */
755 2 : total_space = (uint64) (nblocks - (stats.bitmap_pages + 1)) *
756 2 : stats.space_per_page;
757 :
758 2 : if (total_space == 0)
759 0 : free_percent = 0.0;
760 : else
761 2 : free_percent = 100.0 * stats.free_space / total_space;
762 :
763 : /*
764 : * Build a tuple descriptor for our result type
765 : */
766 2 : if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
767 0 : elog(ERROR, "return type must be a row type");
768 :
769 2 : tupleDesc = BlessTupleDesc(tupleDesc);
770 :
771 : /*
772 : * Build and return the tuple
773 : */
774 2 : values[0] = Int32GetDatum(stats.version);
775 2 : values[1] = Int64GetDatum((int64) stats.bucket_pages);
776 2 : values[2] = Int64GetDatum((int64) stats.overflow_pages);
777 2 : values[3] = Int64GetDatum((int64) stats.bitmap_pages);
778 2 : values[4] = Int64GetDatum((int64) stats.unused_pages);
779 2 : values[5] = Int64GetDatum(stats.live_items);
780 2 : values[6] = Int64GetDatum(stats.dead_items);
781 2 : values[7] = Float8GetDatum(free_percent);
782 2 : tuple = heap_form_tuple(tupleDesc, values, nulls);
783 :
784 2 : PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
785 : }
786 :
787 : /* -------------------------------------------------
788 : * GetHashPageStats()
789 : *
790 : * Collect statistics of single hash page
791 : * -------------------------------------------------
792 : */
793 : static void
794 12 : GetHashPageStats(Page page, HashIndexStat *stats)
795 : {
796 12 : OffsetNumber maxoff = PageGetMaxOffsetNumber(page);
797 : int off;
798 :
799 : /* count live and dead tuples, and free space */
800 12 : for (off = FirstOffsetNumber; off <= maxoff; off++)
801 : {
802 0 : ItemId id = PageGetItemId(page, off);
803 :
804 0 : if (!ItemIdIsDead(id))
805 0 : stats->live_items++;
806 : else
807 0 : stats->dead_items++;
808 : }
809 12 : stats->free_space += PageGetExactFreeSpace(page);
810 12 : }
|