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