Branch data Line data Source code
1 : : /*
2 : : * contrib/pgstattuple/pgstattuple.c
3 : : *
4 : : * Copyright (c) 2001,2002 Tatsuo Ishii
5 : : *
6 : : * Permission to use, copy, modify, and distribute this software and
7 : : * its documentation for any purpose, without fee, and without a
8 : : * written agreement is hereby granted, provided that the above
9 : : * copyright notice and this paragraph and the following two
10 : : * paragraphs appear in all copies.
11 : : *
12 : : * IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT,
13 : : * INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
14 : : * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
15 : : * DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED
16 : : * OF THE POSSIBILITY OF SUCH DAMAGE.
17 : : *
18 : : * THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
19 : : * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 : : * A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS
21 : : * IS" BASIS, AND THE AUTHOR HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE,
22 : : * SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
23 : : */
24 : :
25 : : #include "postgres.h"
26 : :
27 : : #include "access/gist_private.h"
28 : : #include "access/hash.h"
29 : : #include "access/heapam.h"
30 : : #include "access/nbtree.h"
31 : : #include "access/relscan.h"
32 : : #include "access/tableam.h"
33 : : #include "access/xlog.h"
34 : : #include "catalog/namespace.h"
35 : : #include "catalog/pg_am_d.h"
36 : : #include "funcapi.h"
37 : : #include "miscadmin.h"
38 : : #include "pgstattuple.h"
39 : : #include "storage/bufmgr.h"
40 : : #include "storage/lmgr.h"
41 : : #include "utils/varlena.h"
42 : :
43 : 1 : PG_MODULE_MAGIC_EXT(
44 : : .name = "pgstattuple",
45 : : .version = PG_VERSION
46 : : );
47 : :
48 : 1 : PG_FUNCTION_INFO_V1(pgstattuple);
49 : 2 : PG_FUNCTION_INFO_V1(pgstattuple_v1_5);
50 : 1 : PG_FUNCTION_INFO_V1(pgstattuplebyid);
51 : 2 : PG_FUNCTION_INFO_V1(pgstattuplebyid_v1_5);
52 : :
53 : : /*
54 : : * struct pgstattuple_type
55 : : *
56 : : * tuple_percent, dead_tuple_percent and free_percent are computable,
57 : : * so not defined here.
58 : : */
59 : : typedef struct pgstattuple_type
60 : : {
61 : : uint64 table_len;
62 : : uint64 tuple_count;
63 : : uint64 tuple_len;
64 : : uint64 dead_tuple_count;
65 : : uint64 dead_tuple_len;
66 : : uint64 free_space; /* free/reusable space in bytes */
67 : : } pgstattuple_type;
68 : :
69 : : typedef void (*pgstat_page) (pgstattuple_type *, Relation, BlockNumber,
70 : : BufferAccessStrategy);
71 : :
72 : : static Datum build_pgstattuple_type(pgstattuple_type *stat,
73 : : FunctionCallInfo fcinfo);
74 : : static Datum pgstat_relation(Relation rel, FunctionCallInfo fcinfo);
75 : : static Datum pgstat_heap(Relation rel, FunctionCallInfo fcinfo);
76 : : static void pgstat_btree_page(pgstattuple_type *stat,
77 : : Relation rel, BlockNumber blkno,
78 : : BufferAccessStrategy bstrategy);
79 : : static void pgstat_hash_page(pgstattuple_type *stat,
80 : : Relation rel, BlockNumber blkno,
81 : : BufferAccessStrategy bstrategy);
82 : : static void pgstat_gist_page(pgstattuple_type *stat,
83 : : Relation rel, BlockNumber blkno,
84 : : BufferAccessStrategy bstrategy);
85 : : static Datum pgstat_index(Relation rel, BlockNumber start,
86 : : pgstat_page pagefn, FunctionCallInfo fcinfo);
87 : : static void pgstat_index_page(pgstattuple_type *stat, Page page,
88 : : OffsetNumber minoff, OffsetNumber maxoff);
89 : :
90 : : /*
91 : : * build_pgstattuple_type -- build a pgstattuple_type tuple
92 : : */
93 : : static Datum
94 : 9 : build_pgstattuple_type(pgstattuple_type *stat, FunctionCallInfo fcinfo)
95 : : {
96 : : #define NCOLUMNS 9
97 : : #define NCHARS 314
98 : :
99 : : HeapTuple tuple;
100 : : char *values[NCOLUMNS];
101 : : char values_buf[NCOLUMNS][NCHARS];
102 : : int i;
103 : : double tuple_percent;
104 : : double dead_tuple_percent;
105 : : double free_percent; /* free/reusable space in % */
106 : : TupleDesc tupdesc;
107 : : AttInMetadata *attinmeta;
108 : :
109 : : /* Build a tuple descriptor for our result type */
110 [ - + ]: 9 : if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
111 [ # # ]: 0 : elog(ERROR, "return type must be a row type");
112 : :
113 : : /*
114 : : * Generate attribute metadata needed later to produce tuples from raw C
115 : : * strings
116 : : */
117 : 9 : attinmeta = TupleDescGetAttInMetadata(tupdesc);
118 : :
119 [ + + ]: 9 : if (stat->table_len == 0)
120 : : {
121 : 8 : tuple_percent = 0.0;
122 : 8 : dead_tuple_percent = 0.0;
123 : 8 : free_percent = 0.0;
124 : : }
125 : : else
126 : : {
127 : 1 : tuple_percent = 100.0 * stat->tuple_len / stat->table_len;
128 : 1 : dead_tuple_percent = 100.0 * stat->dead_tuple_len / stat->table_len;
129 : 1 : free_percent = 100.0 * stat->free_space / stat->table_len;
130 : : }
131 : :
132 : : /*
133 : : * Prepare a values array for constructing the tuple. This should be an
134 : : * array of C strings which will be processed later by the appropriate
135 : : * "in" functions.
136 : : */
137 [ + + ]: 90 : for (i = 0; i < NCOLUMNS; i++)
138 : 81 : values[i] = values_buf[i];
139 : 9 : i = 0;
140 : 9 : snprintf(values[i++], NCHARS, INT64_FORMAT, stat->table_len);
141 : 9 : snprintf(values[i++], NCHARS, INT64_FORMAT, stat->tuple_count);
142 : 9 : snprintf(values[i++], NCHARS, INT64_FORMAT, stat->tuple_len);
143 : 9 : snprintf(values[i++], NCHARS, "%.2f", tuple_percent);
144 : 9 : snprintf(values[i++], NCHARS, INT64_FORMAT, stat->dead_tuple_count);
145 : 9 : snprintf(values[i++], NCHARS, INT64_FORMAT, stat->dead_tuple_len);
146 : 9 : snprintf(values[i++], NCHARS, "%.2f", dead_tuple_percent);
147 : 9 : snprintf(values[i++], NCHARS, INT64_FORMAT, stat->free_space);
148 : 9 : snprintf(values[i++], NCHARS, "%.2f", free_percent);
149 : :
150 : : /* build a tuple */
151 : 9 : tuple = BuildTupleFromCStrings(attinmeta, values);
152 : :
153 : : /* make the tuple into a datum */
154 : 9 : return HeapTupleGetDatum(tuple);
155 : : }
156 : :
157 : : /* ----------
158 : : * pgstattuple:
159 : : * returns live/dead tuples info
160 : : *
161 : : * C FUNCTION definition
162 : : * pgstattuple(text) returns pgstattuple_type
163 : : *
164 : : * The superuser() check here must be kept as the library might be upgraded
165 : : * without the extension being upgraded, meaning that in pre-1.5 installations
166 : : * these functions could be called by any user.
167 : : * ----------
168 : : */
169 : :
170 : : Datum
171 : 0 : pgstattuple(PG_FUNCTION_ARGS)
172 : : {
173 : 0 : text *relname = PG_GETARG_TEXT_PP(0);
174 : : RangeVar *relrv;
175 : : Relation rel;
176 : :
177 [ # # ]: 0 : if (!superuser())
178 [ # # ]: 0 : ereport(ERROR,
179 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
180 : : errmsg("must be superuser to use pgstattuple functions")));
181 : :
182 : : /* open relation */
183 : 0 : relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
184 : 0 : rel = relation_openrv(relrv, AccessShareLock);
185 : :
186 : 0 : PG_RETURN_DATUM(pgstat_relation(rel, fcinfo));
187 : : }
188 : :
189 : : /*
190 : : * As of pgstattuple version 1.5, we no longer need to check if the user
191 : : * is a superuser because we REVOKE EXECUTE on the function from PUBLIC.
192 : : * Users can then grant access to it based on their policies.
193 : : *
194 : : * Otherwise identical to pgstattuple (above).
195 : : */
196 : : Datum
197 : 10 : pgstattuple_v1_5(PG_FUNCTION_ARGS)
198 : : {
199 : 10 : text *relname = PG_GETARG_TEXT_PP(0);
200 : : RangeVar *relrv;
201 : : Relation rel;
202 : :
203 : : /* open relation */
204 : 10 : relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname));
205 : 10 : rel = relation_openrv(relrv, AccessShareLock);
206 : :
207 : 10 : PG_RETURN_DATUM(pgstat_relation(rel, fcinfo));
208 : : }
209 : :
210 : : /* Must keep superuser() check, see above. */
211 : : Datum
212 : 0 : pgstattuplebyid(PG_FUNCTION_ARGS)
213 : : {
214 : 0 : Oid relid = PG_GETARG_OID(0);
215 : : Relation rel;
216 : :
217 [ # # ]: 0 : if (!superuser())
218 [ # # ]: 0 : ereport(ERROR,
219 : : (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
220 : : errmsg("must be superuser to use pgstattuple functions")));
221 : :
222 : : /* open relation */
223 : 0 : rel = relation_open(relid, AccessShareLock);
224 : :
225 : 0 : PG_RETURN_DATUM(pgstat_relation(rel, fcinfo));
226 : : }
227 : :
228 : : /* Remove superuser() check for 1.5 version, see above */
229 : : Datum
230 : 3 : pgstattuplebyid_v1_5(PG_FUNCTION_ARGS)
231 : : {
232 : 3 : Oid relid = PG_GETARG_OID(0);
233 : : Relation rel;
234 : :
235 : : /* open relation */
236 : 3 : rel = relation_open(relid, AccessShareLock);
237 : :
238 : 3 : PG_RETURN_DATUM(pgstat_relation(rel, fcinfo));
239 : : }
240 : :
241 : : /*
242 : : * check_relation_supported
243 : : *
244 : : * Check that a relation can be read by this session.
245 : : */
246 : : void
247 : 27 : check_relation_supported(Relation rel)
248 : : {
249 : : /*
250 : : * Reject attempts to read non-local temporary relations; we would be
251 : : * likely to get wrong data since we have no visibility into the owning
252 : : * session's local buffers.
253 : : */
254 [ - + - - ]: 27 : if (RELATION_IS_OTHER_TEMP(rel))
255 : : {
256 [ # # ]: 0 : if (rel->rd_rel->relkind == RELKIND_INDEX)
257 [ # # ]: 0 : ereport(ERROR,
258 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
259 : : errmsg("cannot access temporary indexes of other sessions")));
260 : : else
261 [ # # ]: 0 : ereport(ERROR,
262 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
263 : : errmsg("cannot access temporary tables of other sessions")));
264 : : }
265 : :
266 [ - + - - ]: 27 : if (!RelationIsPermanent(rel) && RecoveryInProgress())
267 [ # # ]: 0 : ereport(ERROR,
268 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
269 : : errmsg("cannot access temporary or unlogged relations during recovery")));
270 : 27 : }
271 : :
272 : : /*
273 : : * pgstat_relation
274 : : */
275 : : static Datum
276 : 13 : pgstat_relation(Relation rel, FunctionCallInfo fcinfo)
277 : : {
278 : : const char *err;
279 : :
280 : 13 : check_relation_supported(rel);
281 : :
282 [ + + + + : 13 : if (RELKIND_HAS_TABLE_AM(rel->rd_rel->relkind) ||
+ - ]
283 [ + + ]: 5 : rel->rd_rel->relkind == RELKIND_SEQUENCE)
284 : : {
285 : 9 : return pgstat_heap(rel, fcinfo);
286 : : }
287 [ - + ]: 4 : else if (rel->rd_rel->relkind == RELKIND_INDEX)
288 : : {
289 : : /* see pgstatindex_impl */
290 [ # # ]: 0 : if (!rel->rd_index->indisvalid)
291 [ # # ]: 0 : ereport(ERROR,
292 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
293 : : errmsg("index \"%s\" is not valid",
294 : : RelationGetRelationName(rel))));
295 : :
296 [ # # # # : 0 : switch (rel->rd_rel->relam)
# # # ]
297 : : {
298 : 0 : case BTREE_AM_OID:
299 : 0 : return pgstat_index(rel, BTREE_METAPAGE + 1,
300 : : pgstat_btree_page, fcinfo);
301 : 0 : case HASH_AM_OID:
302 : 0 : return pgstat_index(rel, HASH_METAPAGE + 1,
303 : : pgstat_hash_page, fcinfo);
304 : 0 : case GIST_AM_OID:
305 : 0 : return pgstat_index(rel, GIST_ROOT_BLKNO + 1,
306 : : pgstat_gist_page, fcinfo);
307 : 0 : case GIN_AM_OID:
308 : 0 : err = "gin index";
309 : 0 : break;
310 : 0 : case SPGIST_AM_OID:
311 : 0 : err = "spgist index";
312 : 0 : break;
313 : 0 : case BRIN_AM_OID:
314 : 0 : err = "brin index";
315 : 0 : break;
316 : 0 : default:
317 : 0 : err = "unknown index";
318 : 0 : break;
319 : : }
320 [ # # ]: 0 : ereport(ERROR,
321 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
322 : : errmsg("index \"%s\" (%s) is not supported",
323 : : RelationGetRelationName(rel), err)));
324 : : }
325 : : else
326 : : {
327 [ + - ]: 4 : ereport(ERROR,
328 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
329 : : errmsg("cannot get tuple-level statistics for relation \"%s\"",
330 : : RelationGetRelationName(rel)),
331 : : errdetail_relkind_not_supported(rel->rd_rel->relkind)));
332 : : }
333 : :
334 : : return 0; /* should not happen */
335 : : }
336 : :
337 : : /*
338 : : * pgstat_heap -- returns live/dead tuples info in a heap
339 : : */
340 : : static Datum
341 : 9 : pgstat_heap(Relation rel, FunctionCallInfo fcinfo)
342 : : {
343 : : TableScanDesc scan;
344 : : HeapScanDesc hscan;
345 : : HeapTuple tuple;
346 : : BlockNumber nblocks;
347 : 9 : BlockNumber block = 0; /* next block to count free space in */
348 : : BlockNumber tupblock;
349 : : Buffer buffer;
350 : 9 : pgstattuple_type stat = {0};
351 : : SnapshotData SnapshotDirty;
352 : :
353 : : /*
354 : : * Sequences always use heap AM, but they don't show that in the catalogs.
355 : : */
356 [ + + ]: 9 : if (rel->rd_rel->relkind != RELKIND_SEQUENCE &&
357 [ - + ]: 8 : rel->rd_rel->relam != HEAP_TABLE_AM_OID)
358 [ # # ]: 0 : ereport(ERROR,
359 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
360 : : errmsg("only heap AM is supported")));
361 : :
362 : : /* Disable syncscan because we assume we scan from block zero upwards */
363 : 9 : scan = table_beginscan_strat(rel, SnapshotAny, 0, NULL, true, false);
364 : 9 : hscan = (HeapScanDesc) scan;
365 : :
366 : 9 : InitDirtySnapshot(SnapshotDirty);
367 : :
368 : 9 : nblocks = hscan->rs_nblocks; /* # blocks to be scanned */
369 : :
370 : : /* scan the relation */
371 [ + + ]: 10 : while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL)
372 : : {
373 [ - + ]: 1 : CHECK_FOR_INTERRUPTS();
374 : :
375 : : /* must hold a buffer lock to call HeapTupleSatisfiesVisibility */
376 : 1 : LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_SHARE);
377 : :
378 [ + - ]: 1 : if (HeapTupleSatisfiesVisibility(tuple, &SnapshotDirty, hscan->rs_cbuf))
379 : : {
380 : 1 : stat.tuple_len += tuple->t_len;
381 : 1 : stat.tuple_count++;
382 : : }
383 : : else
384 : : {
385 : 0 : stat.dead_tuple_len += tuple->t_len;
386 : 0 : stat.dead_tuple_count++;
387 : : }
388 : :
389 : 1 : LockBuffer(hscan->rs_cbuf, BUFFER_LOCK_UNLOCK);
390 : :
391 : : /*
392 : : * To avoid physically reading the table twice, try to do the
393 : : * free-space scan in parallel with the heap scan. However,
394 : : * heap_getnext may find no tuples on a given page, so we cannot
395 : : * simply examine the pages returned by the heap scan.
396 : : */
397 : 1 : tupblock = ItemPointerGetBlockNumber(&tuple->t_self);
398 : :
399 [ + + ]: 2 : while (block <= tupblock)
400 : : {
401 [ - + ]: 1 : CHECK_FOR_INTERRUPTS();
402 : :
403 : 1 : buffer = ReadBufferExtended(rel, MAIN_FORKNUM, block,
404 : : RBM_NORMAL, hscan->rs_strategy);
405 : 1 : LockBuffer(buffer, BUFFER_LOCK_SHARE);
406 : 1 : stat.free_space += PageGetExactFreeSpace(BufferGetPage(buffer));
407 : 1 : UnlockReleaseBuffer(buffer);
408 : 1 : block++;
409 : : }
410 : : }
411 : :
412 [ - + ]: 9 : while (block < nblocks)
413 : : {
414 [ # # ]: 0 : CHECK_FOR_INTERRUPTS();
415 : :
416 : 0 : buffer = ReadBufferExtended(rel, MAIN_FORKNUM, block,
417 : : RBM_NORMAL, hscan->rs_strategy);
418 : 0 : LockBuffer(buffer, BUFFER_LOCK_SHARE);
419 : 0 : stat.free_space += PageGetExactFreeSpace(BufferGetPage(buffer));
420 : 0 : UnlockReleaseBuffer(buffer);
421 : 0 : block++;
422 : : }
423 : :
424 : 9 : table_endscan(scan);
425 : 9 : relation_close(rel, AccessShareLock);
426 : :
427 : 9 : stat.table_len = (uint64) nblocks * BLCKSZ;
428 : :
429 : 9 : return build_pgstattuple_type(&stat, fcinfo);
430 : : }
431 : :
432 : : /*
433 : : * pgstat_btree_page -- check tuples in a btree page
434 : : */
435 : : static void
436 : 0 : pgstat_btree_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno,
437 : : BufferAccessStrategy bstrategy)
438 : : {
439 : : Buffer buf;
440 : : Page page;
441 : :
442 : 0 : buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, bstrategy);
443 : 0 : LockBuffer(buf, BT_READ);
444 : 0 : page = BufferGetPage(buf);
445 : :
446 : : /* Page is valid, see what to do with it */
447 [ # # ]: 0 : if (PageIsNew(page))
448 : : {
449 : : /* fully empty page */
450 : 0 : stat->free_space += BLCKSZ;
451 : : }
452 [ # # ]: 0 : else if (PageGetSpecialSize(page) == MAXALIGN(sizeof(BTPageOpaqueData)))
453 : : {
454 : : BTPageOpaque opaque;
455 : :
456 : 0 : opaque = BTPageGetOpaque(page);
457 [ # # ]: 0 : if (P_IGNORE(opaque))
458 : : {
459 : : /* deleted or half-dead page */
460 : 0 : stat->free_space += BLCKSZ;
461 : : }
462 [ # # ]: 0 : else if (P_ISLEAF(opaque))
463 : : {
464 [ # # ]: 0 : pgstat_index_page(stat, page, P_FIRSTDATAKEY(opaque),
465 : 0 : PageGetMaxOffsetNumber(page));
466 : : }
467 : : else
468 : : {
469 : : /* internal page */
470 : : }
471 : : }
472 : :
473 : 0 : _bt_relbuf(rel, buf);
474 : 0 : }
475 : :
476 : : /*
477 : : * pgstat_hash_page -- check tuples in a hash page
478 : : */
479 : : static void
480 : 0 : pgstat_hash_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno,
481 : : BufferAccessStrategy bstrategy)
482 : : {
483 : : Buffer buf;
484 : : Page page;
485 : :
486 : 0 : buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, bstrategy);
487 : 0 : LockBuffer(buf, HASH_READ);
488 : 0 : page = BufferGetPage(buf);
489 : :
490 [ # # ]: 0 : if (PageIsNew(page))
491 : : {
492 : : /* fully empty page */
493 : 0 : stat->free_space += BLCKSZ;
494 : : }
495 [ # # ]: 0 : else if (PageGetSpecialSize(page) == MAXALIGN(sizeof(HashPageOpaqueData)))
496 : : {
497 : : HashPageOpaque opaque;
498 : :
499 : 0 : opaque = HashPageGetOpaque(page);
500 [ # # # ]: 0 : switch (opaque->hasho_flag & LH_PAGE_TYPE)
501 : : {
502 : 0 : case LH_UNUSED_PAGE:
503 : 0 : stat->free_space += BLCKSZ;
504 : 0 : break;
505 : 0 : case LH_BUCKET_PAGE:
506 : : case LH_OVERFLOW_PAGE:
507 : 0 : pgstat_index_page(stat, page, FirstOffsetNumber,
508 : 0 : PageGetMaxOffsetNumber(page));
509 : 0 : break;
510 : 0 : case LH_BITMAP_PAGE:
511 : : case LH_META_PAGE:
512 : : default:
513 : 0 : break;
514 : : }
515 : : }
516 : : else
517 : : {
518 : : /* maybe corrupted */
519 : : }
520 : :
521 : 0 : _hash_relbuf(rel, buf);
522 : 0 : }
523 : :
524 : : /*
525 : : * pgstat_gist_page -- check tuples in a gist page
526 : : */
527 : : static void
528 : 0 : pgstat_gist_page(pgstattuple_type *stat, Relation rel, BlockNumber blkno,
529 : : BufferAccessStrategy bstrategy)
530 : : {
531 : : Buffer buf;
532 : : Page page;
533 : :
534 : 0 : buf = ReadBufferExtended(rel, MAIN_FORKNUM, blkno, RBM_NORMAL, bstrategy);
535 : 0 : LockBuffer(buf, GIST_SHARE);
536 : 0 : page = BufferGetPage(buf);
537 [ # # ]: 0 : if (PageIsNew(page))
538 : : {
539 : : /* fully empty page */
540 : 0 : stat->free_space += BLCKSZ;
541 : : }
542 [ # # ]: 0 : else if (PageGetSpecialSize(page) == MAXALIGN(sizeof(GISTPageOpaqueData)))
543 : : {
544 [ # # ]: 0 : if (GistPageIsLeaf(page))
545 : : {
546 : 0 : pgstat_index_page(stat, page, FirstOffsetNumber,
547 : 0 : PageGetMaxOffsetNumber(page));
548 : : }
549 : : else
550 : : {
551 : : /* root or node */
552 : : }
553 : : }
554 : :
555 : 0 : UnlockReleaseBuffer(buf);
556 : 0 : }
557 : :
558 : : /*
559 : : * pgstat_index -- returns live/dead tuples info in a generic index
560 : : */
561 : : static Datum
562 : 0 : pgstat_index(Relation rel, BlockNumber start, pgstat_page pagefn,
563 : : FunctionCallInfo fcinfo)
564 : : {
565 : : BlockNumber nblocks;
566 : : BlockNumber blkno;
567 : : BufferAccessStrategy bstrategy;
568 : 0 : pgstattuple_type stat = {0};
569 : :
570 : : /* prepare access strategy for this index */
571 : 0 : bstrategy = GetAccessStrategy(BAS_BULKREAD);
572 : :
573 : 0 : blkno = start;
574 : : for (;;)
575 : : {
576 : : /* Get the current relation length */
577 : 0 : LockRelationForExtension(rel, ExclusiveLock);
578 : 0 : nblocks = RelationGetNumberOfBlocks(rel);
579 : 0 : UnlockRelationForExtension(rel, ExclusiveLock);
580 : :
581 : : /* Quit if we've scanned the whole relation */
582 [ # # ]: 0 : if (blkno >= nblocks)
583 : : {
584 : 0 : stat.table_len = (uint64) nblocks * BLCKSZ;
585 : :
586 : 0 : break;
587 : : }
588 : :
589 [ # # ]: 0 : for (; blkno < nblocks; blkno++)
590 : : {
591 [ # # ]: 0 : CHECK_FOR_INTERRUPTS();
592 : :
593 : 0 : pagefn(&stat, rel, blkno, bstrategy);
594 : : }
595 : : }
596 : :
597 : 0 : relation_close(rel, AccessShareLock);
598 : :
599 : 0 : return build_pgstattuple_type(&stat, fcinfo);
600 : : }
601 : :
602 : : /*
603 : : * pgstat_index_page -- for generic index page
604 : : */
605 : : static void
606 : 0 : pgstat_index_page(pgstattuple_type *stat, Page page,
607 : : OffsetNumber minoff, OffsetNumber maxoff)
608 : : {
609 : : OffsetNumber i;
610 : :
611 : 0 : stat->free_space += PageGetExactFreeSpace(page);
612 : :
613 [ # # ]: 0 : for (i = minoff; i <= maxoff; i = OffsetNumberNext(i))
614 : : {
615 : 0 : ItemId itemid = PageGetItemId(page, i);
616 : :
617 [ # # ]: 0 : if (ItemIdIsDead(itemid))
618 : : {
619 : 0 : stat->dead_tuple_count++;
620 : 0 : stat->dead_tuple_len += ItemIdGetLength(itemid);
621 : : }
622 : : else
623 : : {
624 : 0 : stat->tuple_count++;
625 : 0 : stat->tuple_len += ItemIdGetLength(itemid);
626 : : }
627 : : }
628 : 0 : }
|