PostgreSQL Source Code git master
pgstat_relation.c
Go to the documentation of this file.
1/* -------------------------------------------------------------------------
2 *
3 * pgstat_relation.c
4 * Implementation of relation statistics.
5 *
6 * This file contains the implementation of function relation. It is kept
7 * separate from pgstat.c to enforce the line between the statistics access /
8 * storage implementation and the details about individual types of
9 * statistics.
10 *
11 * Copyright (c) 2001-2025, PostgreSQL Global Development Group
12 *
13 * IDENTIFICATION
14 * src/backend/utils/activity/pgstat_relation.c
15 * -------------------------------------------------------------------------
16 */
17
18#include "postgres.h"
19
21#include "access/xact.h"
22#include "catalog/catalog.h"
23#include "utils/memutils.h"
25#include "utils/rel.h"
26#include "utils/timestamp.h"
27
28
29/* Record that's written to 2PC state file when pgstat state is persisted */
31{
32 PgStat_Counter tuples_inserted; /* tuples inserted in xact */
33 PgStat_Counter tuples_updated; /* tuples updated in xact */
34 PgStat_Counter tuples_deleted; /* tuples deleted in xact */
35 /* tuples i/u/d prior to truncate/drop */
39 Oid id; /* table's OID */
40 bool shared; /* is it a shared catalog? */
41 bool truncdropped; /* was the relation truncated/dropped? */
43
44
45static PgStat_TableStatus *pgstat_prep_relation_pending(Oid rel_id, bool isshared);
46static void add_tabstat_xact_level(PgStat_TableStatus *pgstat_info, int nest_level);
47static void ensure_tabstat_xact_level(PgStat_TableStatus *pgstat_info);
48static void save_truncdrop_counters(PgStat_TableXactStatus *trans, bool is_drop);
50
51
52/*
53 * Copy stats between relations. This is used for things like REINDEX
54 * CONCURRENTLY.
55 */
56void
58{
59 PgStat_StatTabEntry *srcstats;
60 PgStatShared_Relation *dstshstats;
61 PgStat_EntryRef *dst_ref;
62
63 srcstats = pgstat_fetch_stat_tabentry_ext(src->rd_rel->relisshared,
64 RelationGetRelid(src));
65 if (!srcstats)
66 return;
67
69 dst->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
71 false);
72
73 dstshstats = (PgStatShared_Relation *) dst_ref->shared_stats;
74 dstshstats->stats = *srcstats;
75
76 pgstat_unlock_entry(dst_ref);
77}
78
79/*
80 * Initialize a relcache entry to count access statistics. Called whenever a
81 * relation is opened.
82 *
83 * We assume that a relcache entry's pgstat_info field is zeroed by relcache.c
84 * when the relcache entry is made; thereafter it is long-lived data.
85 *
86 * This does not create a reference to a stats entry in shared memory, nor
87 * allocate memory for the pending stats. That happens in
88 * pgstat_assoc_relation().
89 */
90void
92{
93 char relkind = rel->rd_rel->relkind;
94
95 /*
96 * We only count stats for relations with storage and partitioned tables
97 */
98 if (!RELKIND_HAS_STORAGE(relkind) && relkind != RELKIND_PARTITIONED_TABLE)
99 {
100 rel->pgstat_enabled = false;
101 rel->pgstat_info = NULL;
102 return;
103 }
104
106 {
107 if (rel->pgstat_info)
109
110 /* We're not counting at all */
111 rel->pgstat_enabled = false;
112 rel->pgstat_info = NULL;
113 return;
114 }
115
116 rel->pgstat_enabled = true;
117}
118
119/*
120 * Prepare for statistics for this relation to be collected.
121 *
122 * This ensures we have a reference to the stats entry before stats can be
123 * generated. That is important because a relation drop in another connection
124 * could otherwise lead to the stats entry being dropped, which then later
125 * would get recreated when flushing stats.
126 *
127 * This is separate from pgstat_init_relation() as it is not uncommon for
128 * relcache entries to be opened without ever getting stats reported.
129 */
130void
132{
134 Assert(rel->pgstat_info == NULL);
135
136 /* Else find or make the PgStat_TableStatus entry, and update link */
138 rel->rd_rel->relisshared);
139
140 /* don't allow link a stats to multiple relcache entries */
141 Assert(rel->pgstat_info->relation == NULL);
142
143 /* mark this relation as the owner */
144 rel->pgstat_info->relation = rel;
145}
146
147/*
148 * Break the mutual link between a relcache entry and pending stats entry.
149 * This must be called whenever one end of the link is removed.
150 */
151void
153{
154 /* remove the link to stats info if any */
155 if (rel->pgstat_info == NULL)
156 return;
157
158 /* link sanity check */
159 Assert(rel->pgstat_info->relation == rel);
160 rel->pgstat_info->relation = NULL;
161 rel->pgstat_info = NULL;
162}
163
164/*
165 * Ensure that stats are dropped if transaction aborts.
166 */
167void
169{
171 rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
172 RelationGetRelid(rel));
173}
174
175/*
176 * Ensure that stats are dropped if transaction commits.
177 */
178void
180{
181 int nest_level = GetCurrentTransactionNestLevel();
182 PgStat_TableStatus *pgstat_info;
183
185 rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
186 RelationGetRelid(rel));
187
189 return;
190
191 /*
192 * Transactionally set counters to 0. That ensures that accesses to
193 * pg_stat_xact_all_tables inside the transaction show 0.
194 */
195 pgstat_info = rel->pgstat_info;
196 if (pgstat_info->trans &&
197 pgstat_info->trans->nest_level == nest_level)
198 {
199 save_truncdrop_counters(pgstat_info->trans, true);
200 pgstat_info->trans->tuples_inserted = 0;
201 pgstat_info->trans->tuples_updated = 0;
202 pgstat_info->trans->tuples_deleted = 0;
203 }
204}
205
206/*
207 * Report that the table was just vacuumed and flush IO statistics.
208 */
209void
210pgstat_report_vacuum(Oid tableoid, bool shared,
211 PgStat_Counter livetuples, PgStat_Counter deadtuples)
212{
213 PgStat_EntryRef *entry_ref;
214 PgStatShared_Relation *shtabentry;
215 PgStat_StatTabEntry *tabentry;
216 Oid dboid = (shared ? InvalidOid : MyDatabaseId);
217 TimestampTz ts;
218
220 return;
221
222 /* Store the data in the table's hash table entry. */
223 ts = GetCurrentTimestamp();
224
225 /* block acquiring lock for the same reason as pgstat_report_autovac() */
227 dboid, tableoid, false);
228
229 shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats;
230 tabentry = &shtabentry->stats;
231
232 tabentry->live_tuples = livetuples;
233 tabentry->dead_tuples = deadtuples;
234
235 /*
236 * It is quite possible that a non-aggressive VACUUM ended up skipping
237 * various pages, however, we'll zero the insert counter here regardless.
238 * It's currently used only to track when we need to perform an "insert"
239 * autovacuum, which are mainly intended to freeze newly inserted tuples.
240 * Zeroing this may just mean we'll not try to vacuum the table again
241 * until enough tuples have been inserted to trigger another insert
242 * autovacuum. An anti-wraparound autovacuum will catch any persistent
243 * stragglers.
244 */
245 tabentry->ins_since_vacuum = 0;
246
248 {
249 tabentry->last_autovacuum_time = ts;
250 tabentry->autovacuum_count++;
251 }
252 else
253 {
254 tabentry->last_vacuum_time = ts;
255 tabentry->vacuum_count++;
256 }
257
258 pgstat_unlock_entry(entry_ref);
259
260 /*
261 * Flush IO statistics now. pgstat_report_stat() will flush IO stats,
262 * however this will not be called until after an entire autovacuum cycle
263 * is done -- which will likely vacuum many relations -- or until the
264 * VACUUM command has processed all tables and committed.
265 */
266 pgstat_flush_io(false);
268}
269
270/*
271 * Report that the table was just analyzed and flush IO statistics.
272 *
273 * Caller must provide new live- and dead-tuples estimates, as well as a
274 * flag indicating whether to reset the mod_since_analyze counter.
275 */
276void
278 PgStat_Counter livetuples, PgStat_Counter deadtuples,
279 bool resetcounter)
280{
281 PgStat_EntryRef *entry_ref;
282 PgStatShared_Relation *shtabentry;
283 PgStat_StatTabEntry *tabentry;
284 Oid dboid = (rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId);
285
287 return;
288
289 /*
290 * Unlike VACUUM, ANALYZE might be running inside a transaction that has
291 * already inserted and/or deleted rows in the target table. ANALYZE will
292 * have counted such rows as live or dead respectively. Because we will
293 * report our counts of such rows at transaction end, we should subtract
294 * off these counts from the update we're making now, else they'll be
295 * double-counted after commit. (This approach also ensures that the
296 * shared stats entry ends up with the right numbers if we abort instead
297 * of committing.)
298 *
299 * Waste no time on partitioned tables, though.
300 */
302 rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
303 {
305
306 for (trans = rel->pgstat_info->trans; trans; trans = trans->upper)
307 {
308 livetuples -= trans->tuples_inserted - trans->tuples_deleted;
309 deadtuples -= trans->tuples_updated + trans->tuples_deleted;
310 }
311 /* count stuff inserted by already-aborted subxacts, too */
312 deadtuples -= rel->pgstat_info->counts.delta_dead_tuples;
313 /* Since ANALYZE's counts are estimates, we could have underflowed */
314 livetuples = Max(livetuples, 0);
315 deadtuples = Max(deadtuples, 0);
316 }
317
318 /* block acquiring lock for the same reason as pgstat_report_autovac() */
320 RelationGetRelid(rel),
321 false);
322 /* can't get dropped while accessed */
323 Assert(entry_ref != NULL && entry_ref->shared_stats != NULL);
324
325 shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats;
326 tabentry = &shtabentry->stats;
327
328 tabentry->live_tuples = livetuples;
329 tabentry->dead_tuples = deadtuples;
330
331 /*
332 * If commanded, reset mod_since_analyze to zero. This forgets any
333 * changes that were committed while the ANALYZE was in progress, but we
334 * have no good way to estimate how many of those there were.
335 */
336 if (resetcounter)
337 tabentry->mod_since_analyze = 0;
338
340 {
342 tabentry->autoanalyze_count++;
343 }
344 else
345 {
347 tabentry->analyze_count++;
348 }
349
350 pgstat_unlock_entry(entry_ref);
351
352 /* see pgstat_report_vacuum() */
353 pgstat_flush_io(false);
355}
356
357/*
358 * count a tuple insertion of n tuples
359 */
360void
362{
364 {
365 PgStat_TableStatus *pgstat_info = rel->pgstat_info;
366
367 ensure_tabstat_xact_level(pgstat_info);
368 pgstat_info->trans->tuples_inserted += n;
369 }
370}
371
372/*
373 * count a tuple update
374 */
375void
376pgstat_count_heap_update(Relation rel, bool hot, bool newpage)
377{
378 Assert(!(hot && newpage));
379
381 {
382 PgStat_TableStatus *pgstat_info = rel->pgstat_info;
383
384 ensure_tabstat_xact_level(pgstat_info);
385 pgstat_info->trans->tuples_updated++;
386
387 /*
388 * tuples_hot_updated and tuples_newpage_updated counters are
389 * nontransactional, so just advance them
390 */
391 if (hot)
392 pgstat_info->counts.tuples_hot_updated++;
393 else if (newpage)
394 pgstat_info->counts.tuples_newpage_updated++;
395 }
396}
397
398/*
399 * count a tuple deletion
400 */
401void
403{
405 {
406 PgStat_TableStatus *pgstat_info = rel->pgstat_info;
407
408 ensure_tabstat_xact_level(pgstat_info);
409 pgstat_info->trans->tuples_deleted++;
410 }
411}
412
413/*
414 * update tuple counters due to truncate
415 */
416void
418{
420 {
421 PgStat_TableStatus *pgstat_info = rel->pgstat_info;
422
423 ensure_tabstat_xact_level(pgstat_info);
424 save_truncdrop_counters(pgstat_info->trans, false);
425 pgstat_info->trans->tuples_inserted = 0;
426 pgstat_info->trans->tuples_updated = 0;
427 pgstat_info->trans->tuples_deleted = 0;
428 }
429}
430
431/*
432 * update dead-tuples count
433 *
434 * The semantics of this are that we are reporting the nontransactional
435 * recovery of "delta" dead tuples; so delta_dead_tuples decreases
436 * rather than increasing, and the change goes straight into the per-table
437 * counter, not into transactional state.
438 */
439void
441{
443 {
444 PgStat_TableStatus *pgstat_info = rel->pgstat_info;
445
446 pgstat_info->counts.delta_dead_tuples -= delta;
447 }
448}
449
450/*
451 * Support function for the SQL-callable pgstat* functions. Returns
452 * the collected statistics for one table or NULL. NULL doesn't mean
453 * that the table doesn't exist, just that there are no statistics, so the
454 * caller is better off to report ZERO instead.
455 */
458{
460}
461
462/*
463 * More efficient version of pgstat_fetch_stat_tabentry(), allowing to specify
464 * whether the to-be-accessed table is a shared relation or not.
465 */
468{
469 Oid dboid = (shared ? InvalidOid : MyDatabaseId);
470
471 return (PgStat_StatTabEntry *)
473}
474
475/*
476 * find any existing PgStat_TableStatus entry for rel
477 *
478 * Find any existing PgStat_TableStatus entry for rel_id in the current
479 * database. If not found, try finding from shared tables.
480 *
481 * If an entry is found, copy it and increment the copy's counters with their
482 * subtransaction counterparts, then return the copy. The caller may need to
483 * pfree() the copy.
484 *
485 * If no entry found, return NULL, don't create a new one.
486 */
489{
490 PgStat_EntryRef *entry_ref;
492 PgStat_TableStatus *tabentry = NULL;
493 PgStat_TableStatus *tablestatus = NULL;
494
496 if (!entry_ref)
497 {
499 if (!entry_ref)
500 return tablestatus;
501 }
502
503 tabentry = (PgStat_TableStatus *) entry_ref->pending;
504 tablestatus = palloc(sizeof(PgStat_TableStatus));
505 *tablestatus = *tabentry;
506
507 /*
508 * Reset tablestatus->trans in the copy of PgStat_TableStatus as it may
509 * point to a shared memory area. Its data is saved below, so removing it
510 * does not matter.
511 */
512 tablestatus->trans = NULL;
513
514 /*
515 * Live subtransaction counts are not included yet. This is not a hot
516 * code path so reconcile tuples_inserted, tuples_updated and
517 * tuples_deleted even if the caller may not be interested in this data.
518 */
519 for (trans = tabentry->trans; trans != NULL; trans = trans->upper)
520 {
521 tablestatus->counts.tuples_inserted += trans->tuples_inserted;
522 tablestatus->counts.tuples_updated += trans->tuples_updated;
523 tablestatus->counts.tuples_deleted += trans->tuples_deleted;
524 }
525
526 return tablestatus;
527}
528
529/*
530 * Perform relation stats specific end-of-transaction work. Helper for
531 * AtEOXact_PgStat.
532 *
533 * Transfer transactional insert/update counts into the base tabstat entries.
534 * We don't bother to free any of the transactional state, since it's all in
535 * TopTransactionContext and will go away anyway.
536 */
537void
539{
541
542 for (trans = xact_state->first; trans != NULL; trans = trans->next)
543 {
544 PgStat_TableStatus *tabstat;
545
546 Assert(trans->nest_level == 1);
547 Assert(trans->upper == NULL);
548 tabstat = trans->parent;
549 Assert(tabstat->trans == trans);
550 /* restore pre-truncate/drop stats (if any) in case of aborted xact */
551 if (!isCommit)
553 /* count attempted actions regardless of commit/abort */
554 tabstat->counts.tuples_inserted += trans->tuples_inserted;
555 tabstat->counts.tuples_updated += trans->tuples_updated;
556 tabstat->counts.tuples_deleted += trans->tuples_deleted;
557 if (isCommit)
558 {
559 tabstat->counts.truncdropped = trans->truncdropped;
560 if (trans->truncdropped)
561 {
562 /* forget live/dead stats seen by backend thus far */
563 tabstat->counts.delta_live_tuples = 0;
564 tabstat->counts.delta_dead_tuples = 0;
565 }
566 /* insert adds a live tuple, delete removes one */
567 tabstat->counts.delta_live_tuples +=
568 trans->tuples_inserted - trans->tuples_deleted;
569 /* update and delete each create a dead tuple */
570 tabstat->counts.delta_dead_tuples +=
571 trans->tuples_updated + trans->tuples_deleted;
572 /* insert, update, delete each count as one change event */
573 tabstat->counts.changed_tuples +=
574 trans->tuples_inserted + trans->tuples_updated +
575 trans->tuples_deleted;
576 }
577 else
578 {
579 /* inserted tuples are dead, deleted tuples are unaffected */
580 tabstat->counts.delta_dead_tuples +=
581 trans->tuples_inserted + trans->tuples_updated;
582 /* an aborted xact generates no changed_tuple events */
583 }
584 tabstat->trans = NULL;
585 }
586}
587
588/*
589 * Perform relation stats specific end-of-sub-transaction work. Helper for
590 * AtEOSubXact_PgStat.
591 *
592 * Transfer transactional insert/update counts into the next higher
593 * subtransaction state.
594 */
595void
596AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth)
597{
599 PgStat_TableXactStatus *next_trans;
600
601 for (trans = xact_state->first; trans != NULL; trans = next_trans)
602 {
603 PgStat_TableStatus *tabstat;
604
605 next_trans = trans->next;
606 Assert(trans->nest_level == nestDepth);
607 tabstat = trans->parent;
608 Assert(tabstat->trans == trans);
609
610 if (isCommit)
611 {
612 if (trans->upper && trans->upper->nest_level == nestDepth - 1)
613 {
614 if (trans->truncdropped)
615 {
616 /* propagate the truncate/drop status one level up */
617 save_truncdrop_counters(trans->upper, false);
618 /* replace upper xact stats with ours */
619 trans->upper->tuples_inserted = trans->tuples_inserted;
620 trans->upper->tuples_updated = trans->tuples_updated;
621 trans->upper->tuples_deleted = trans->tuples_deleted;
622 }
623 else
624 {
625 trans->upper->tuples_inserted += trans->tuples_inserted;
626 trans->upper->tuples_updated += trans->tuples_updated;
627 trans->upper->tuples_deleted += trans->tuples_deleted;
628 }
629 tabstat->trans = trans->upper;
630 pfree(trans);
631 }
632 else
633 {
634 /*
635 * When there isn't an immediate parent state, we can just
636 * reuse the record instead of going through a palloc/pfree
637 * pushup (this works since it's all in TopTransactionContext
638 * anyway). We have to re-link it into the parent level,
639 * though, and that might mean pushing a new entry into the
640 * pgStatXactStack.
641 */
642 PgStat_SubXactStatus *upper_xact_state;
643
644 upper_xact_state = pgstat_get_xact_stack_level(nestDepth - 1);
645 trans->next = upper_xact_state->first;
646 upper_xact_state->first = trans;
647 trans->nest_level = nestDepth - 1;
648 }
649 }
650 else
651 {
652 /*
653 * On abort, update top-level tabstat counts, then forget the
654 * subtransaction
655 */
656
657 /* first restore values obliterated by truncate/drop */
659 /* count attempted actions regardless of commit/abort */
660 tabstat->counts.tuples_inserted += trans->tuples_inserted;
661 tabstat->counts.tuples_updated += trans->tuples_updated;
662 tabstat->counts.tuples_deleted += trans->tuples_deleted;
663 /* inserted tuples are dead, deleted tuples are unaffected */
664 tabstat->counts.delta_dead_tuples +=
665 trans->tuples_inserted + trans->tuples_updated;
666 tabstat->trans = trans->upper;
667 pfree(trans);
668 }
669 }
670}
671
672/*
673 * Generate 2PC records for all the pending transaction-dependent relation
674 * stats.
675 */
676void
678{
680
681 for (trans = xact_state->first; trans != NULL; trans = trans->next)
682 {
685
686 Assert(trans->nest_level == 1);
687 Assert(trans->upper == NULL);
688 tabstat = trans->parent;
689 Assert(tabstat->trans == trans);
690
691 record.tuples_inserted = trans->tuples_inserted;
692 record.tuples_updated = trans->tuples_updated;
693 record.tuples_deleted = trans->tuples_deleted;
694 record.inserted_pre_truncdrop = trans->inserted_pre_truncdrop;
695 record.updated_pre_truncdrop = trans->updated_pre_truncdrop;
696 record.deleted_pre_truncdrop = trans->deleted_pre_truncdrop;
697 record.id = tabstat->id;
698 record.shared = tabstat->shared;
699 record.truncdropped = trans->truncdropped;
700
702 &record, sizeof(TwoPhasePgStatRecord));
703 }
704}
705
706/*
707 * All we need do here is unlink the transaction stats state from the
708 * nontransactional state. The nontransactional action counts will be
709 * reported to the stats system immediately, while the effects on live and
710 * dead tuple counts are preserved in the 2PC state file.
711 *
712 * Note: AtEOXact_PgStat_Relations is not called during PREPARE.
713 */
714void
716{
718
719 for (trans = xact_state->first; trans != NULL; trans = trans->next)
720 {
721 PgStat_TableStatus *tabstat;
722
723 tabstat = trans->parent;
724 tabstat->trans = NULL;
725 }
726}
727
728/*
729 * 2PC processing routine for COMMIT PREPARED case.
730 *
731 * Load the saved counts into our local pgstats state.
732 */
733void
735 void *recdata, uint32 len)
736{
738 PgStat_TableStatus *pgstat_info;
739
740 /* Find or create a tabstat entry for the rel */
741 pgstat_info = pgstat_prep_relation_pending(rec->id, rec->shared);
742
743 /* Same math as in AtEOXact_PgStat, commit case */
744 pgstat_info->counts.tuples_inserted += rec->tuples_inserted;
745 pgstat_info->counts.tuples_updated += rec->tuples_updated;
746 pgstat_info->counts.tuples_deleted += rec->tuples_deleted;
747 pgstat_info->counts.truncdropped = rec->truncdropped;
748 if (rec->truncdropped)
749 {
750 /* forget live/dead stats seen by backend thus far */
751 pgstat_info->counts.delta_live_tuples = 0;
752 pgstat_info->counts.delta_dead_tuples = 0;
753 }
754 pgstat_info->counts.delta_live_tuples +=
756 pgstat_info->counts.delta_dead_tuples +=
757 rec->tuples_updated + rec->tuples_deleted;
758 pgstat_info->counts.changed_tuples +=
759 rec->tuples_inserted + rec->tuples_updated +
760 rec->tuples_deleted;
761}
762
763/*
764 * 2PC processing routine for ROLLBACK PREPARED case.
765 *
766 * Load the saved counts into our local pgstats state, but treat them
767 * as aborted.
768 */
769void
771 void *recdata, uint32 len)
772{
774 PgStat_TableStatus *pgstat_info;
775
776 /* Find or create a tabstat entry for the rel */
777 pgstat_info = pgstat_prep_relation_pending(rec->id, rec->shared);
778
779 /* Same math as in AtEOXact_PgStat, abort case */
780 if (rec->truncdropped)
781 {
785 }
786 pgstat_info->counts.tuples_inserted += rec->tuples_inserted;
787 pgstat_info->counts.tuples_updated += rec->tuples_updated;
788 pgstat_info->counts.tuples_deleted += rec->tuples_deleted;
789 pgstat_info->counts.delta_dead_tuples +=
791}
792
793/*
794 * Flush out pending stats for the entry
795 *
796 * If nowait is true, this function returns false if lock could not
797 * immediately acquired, otherwise true is returned.
798 *
799 * Some of the stats are copied to the corresponding pending database stats
800 * entry when successfully flushing.
801 */
802bool
804{
805 Oid dboid;
806 PgStat_TableStatus *lstats; /* pending stats entry */
807 PgStatShared_Relation *shtabstats;
808 PgStat_StatTabEntry *tabentry; /* table entry of shared stats */
809 PgStat_StatDBEntry *dbentry; /* pending database entry */
810
811 dboid = entry_ref->shared_entry->key.dboid;
812 lstats = (PgStat_TableStatus *) entry_ref->pending;
813 shtabstats = (PgStatShared_Relation *) entry_ref->shared_stats;
814
815 /*
816 * Ignore entries that didn't accumulate any actual counts, such as
817 * indexes that were opened by the planner but not used.
818 */
819 if (pg_memory_is_all_zeros(&lstats->counts,
820 sizeof(struct PgStat_TableCounts)))
821 return true;
822
823 if (!pgstat_lock_entry(entry_ref, nowait))
824 return false;
825
826 /* add the values to the shared entry. */
827 tabentry = &shtabstats->stats;
828
829 tabentry->numscans += lstats->counts.numscans;
830 if (lstats->counts.numscans)
831 {
833
834 if (t > tabentry->lastscan)
835 tabentry->lastscan = t;
836 }
837 tabentry->tuples_returned += lstats->counts.tuples_returned;
838 tabentry->tuples_fetched += lstats->counts.tuples_fetched;
839 tabentry->tuples_inserted += lstats->counts.tuples_inserted;
840 tabentry->tuples_updated += lstats->counts.tuples_updated;
841 tabentry->tuples_deleted += lstats->counts.tuples_deleted;
842 tabentry->tuples_hot_updated += lstats->counts.tuples_hot_updated;
844
845 /*
846 * If table was truncated/dropped, first reset the live/dead counters.
847 */
848 if (lstats->counts.truncdropped)
849 {
850 tabentry->live_tuples = 0;
851 tabentry->dead_tuples = 0;
852 tabentry->ins_since_vacuum = 0;
853 }
854
855 tabentry->live_tuples += lstats->counts.delta_live_tuples;
856 tabentry->dead_tuples += lstats->counts.delta_dead_tuples;
857 tabentry->mod_since_analyze += lstats->counts.changed_tuples;
858 tabentry->ins_since_vacuum += lstats->counts.tuples_inserted;
859 tabentry->blocks_fetched += lstats->counts.blocks_fetched;
860 tabentry->blocks_hit += lstats->counts.blocks_hit;
861
862 /* Clamp live_tuples in case of negative delta_live_tuples */
863 tabentry->live_tuples = Max(tabentry->live_tuples, 0);
864 /* Likewise for dead_tuples */
865 tabentry->dead_tuples = Max(tabentry->dead_tuples, 0);
866
867 pgstat_unlock_entry(entry_ref);
868
869 /* The entry was successfully flushed, add the same to database stats */
870 dbentry = pgstat_prep_database_pending(dboid);
871 dbentry->tuples_returned += lstats->counts.tuples_returned;
872 dbentry->tuples_fetched += lstats->counts.tuples_fetched;
873 dbentry->tuples_inserted += lstats->counts.tuples_inserted;
874 dbentry->tuples_updated += lstats->counts.tuples_updated;
875 dbentry->tuples_deleted += lstats->counts.tuples_deleted;
876 dbentry->blocks_fetched += lstats->counts.blocks_fetched;
877 dbentry->blocks_hit += lstats->counts.blocks_hit;
878
879 return true;
880}
881
882void
884{
885 PgStat_TableStatus *pending = (PgStat_TableStatus *) entry_ref->pending;
886
887 if (pending->relation)
889}
890
891/*
892 * Find or create a PgStat_TableStatus entry for rel. New entry is created and
893 * initialized if not exists.
894 */
895static PgStat_TableStatus *
896pgstat_prep_relation_pending(Oid rel_id, bool isshared)
897{
898 PgStat_EntryRef *entry_ref;
899 PgStat_TableStatus *pending;
900
902 isshared ? InvalidOid : MyDatabaseId,
903 rel_id, NULL);
904 pending = entry_ref->pending;
905 pending->id = rel_id;
906 pending->shared = isshared;
907
908 return pending;
909}
910
911/*
912 * add a new (sub)transaction state record
913 */
914static void
915add_tabstat_xact_level(PgStat_TableStatus *pgstat_info, int nest_level)
916{
917 PgStat_SubXactStatus *xact_state;
919
920 /*
921 * If this is the first rel to be modified at the current nest level, we
922 * first have to push a transaction stack entry.
923 */
924 xact_state = pgstat_get_xact_stack_level(nest_level);
925
926 /* Now make a per-table stack entry */
929 sizeof(PgStat_TableXactStatus));
930 trans->nest_level = nest_level;
931 trans->upper = pgstat_info->trans;
932 trans->parent = pgstat_info;
933 trans->next = xact_state->first;
934 xact_state->first = trans;
935 pgstat_info->trans = trans;
936}
937
938/*
939 * Add a new (sub)transaction record if needed.
940 */
941static void
943{
944 int nest_level = GetCurrentTransactionNestLevel();
945
946 if (pgstat_info->trans == NULL ||
947 pgstat_info->trans->nest_level != nest_level)
948 add_tabstat_xact_level(pgstat_info, nest_level);
949}
950
951/*
952 * Whenever a table is truncated/dropped, we save its i/u/d counters so that
953 * they can be cleared, and if the (sub)xact that executed the truncate/drop
954 * later aborts, the counters can be restored to the saved (pre-truncate/drop)
955 * values.
956 *
957 * Note that for truncate we do this on the first truncate in any particular
958 * subxact level only.
959 */
960static void
962{
963 if (!trans->truncdropped || is_drop)
964 {
965 trans->inserted_pre_truncdrop = trans->tuples_inserted;
966 trans->updated_pre_truncdrop = trans->tuples_updated;
967 trans->deleted_pre_truncdrop = trans->tuples_deleted;
968 trans->truncdropped = true;
969 }
970}
971
972/*
973 * restore counters when a truncate aborts
974 */
975static void
977{
978 if (trans->truncdropped)
979 {
980 trans->tuples_inserted = trans->inserted_pre_truncdrop;
981 trans->tuples_updated = trans->updated_pre_truncdrop;
982 trans->tuples_deleted = trans->deleted_pre_truncdrop;
983 }
984}
TimestampTz GetCurrentTimestamp(void)
Definition: timestamp.c:1644
#define PG_USED_FOR_ASSERTS_ONLY
Definition: c.h:204
#define Max(x, y)
Definition: c.h:955
#define Assert(condition)
Definition: c.h:815
uint16_t uint16
Definition: c.h:487
uint32_t uint32
Definition: c.h:488
uint32 TransactionId
Definition: c.h:609
bool IsSharedRelation(Oid relationId)
Definition: catalog.c:273
int64 TimestampTz
Definition: timestamp.h:39
Oid MyDatabaseId
Definition: globals.c:93
if(TABLE==NULL||TABLE_index==NULL)
Definition: isn.c:76
void * MemoryContextAllocZero(MemoryContext context, Size size)
Definition: mcxt.c:1215
MemoryContext TopTransactionContext
Definition: mcxt.c:154
void pfree(void *pointer)
Definition: mcxt.c:1521
void * palloc(Size size)
Definition: mcxt.c:1317
static bool pg_memory_is_all_zeros(const void *ptr, size_t len)
Definition: memutils.h:219
#define AmAutoVacuumWorkerProcess()
Definition: miscadmin.h:381
const void size_t len
PgStat_EntryRef * pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
Definition: pgstat.c:1284
bool pgstat_track_counts
Definition: pgstat.c:204
PgStat_EntryRef * pgstat_fetch_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
Definition: pgstat.c:1322
void * pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
Definition: pgstat.c:950
#define pgstat_should_count_relation(rel)
Definition: pgstat.h:662
int64 PgStat_Counter
Definition: pgstat.h:66
bool pgstat_flush_backend(bool nowait, bits32 flags)
PgStat_StatDBEntry * pgstat_prep_database_pending(Oid dboid)
#define PGSTAT_BACKEND_FLUSH_IO
void pgstat_flush_io(bool nowait)
Definition: pgstat_io.c:171
#define PGSTAT_KIND_RELATION
Definition: pgstat_kind.h:28
struct TwoPhasePgStatRecord TwoPhasePgStatRecord
static void ensure_tabstat_xact_level(PgStat_TableStatus *pgstat_info)
void pgstat_copy_relation_stats(Relation dst, Relation src)
void pgstat_unlink_relation(Relation rel)
void pgstat_count_heap_update(Relation rel, bool hot, bool newpage)
void pgstat_twophase_postcommit(TransactionId xid, uint16 info, void *recdata, uint32 len)
static PgStat_TableStatus * pgstat_prep_relation_pending(Oid rel_id, bool isshared)
void pgstat_assoc_relation(Relation rel)
void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter)
PgStat_StatTabEntry * pgstat_fetch_stat_tabentry_ext(bool shared, Oid reloid)
static void save_truncdrop_counters(PgStat_TableXactStatus *trans, bool is_drop)
void AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
void pgstat_twophase_postabort(TransactionId xid, uint16 info, void *recdata, uint32 len)
PgStat_StatTabEntry * pgstat_fetch_stat_tabentry(Oid relid)
static void add_tabstat_xact_level(PgStat_TableStatus *pgstat_info, int nest_level)
bool pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
void pgstat_create_relation(Relation rel)
void pgstat_update_heap_dead_tuples(Relation rel, int delta)
void pgstat_count_heap_delete(Relation rel)
void pgstat_count_heap_insert(Relation rel, PgStat_Counter n)
PgStat_TableStatus * find_tabstat_entry(Oid rel_id)
void AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit)
void pgstat_report_vacuum(Oid tableoid, bool shared, PgStat_Counter livetuples, PgStat_Counter deadtuples)
void PostPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
void pgstat_count_truncate(Relation rel)
static void restore_truncdrop_counters(PgStat_TableXactStatus *trans)
void pgstat_drop_relation(Relation rel)
void AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth)
void pgstat_init_relation(Relation rel)
void pgstat_relation_delete_pending_cb(PgStat_EntryRef *entry_ref)
void pgstat_unlock_entry(PgStat_EntryRef *entry_ref)
Definition: pgstat_shmem.c:675
bool pgstat_lock_entry(PgStat_EntryRef *entry_ref, bool nowait)
Definition: pgstat_shmem.c:647
PgStat_EntryRef * pgstat_get_entry_ref_locked(PgStat_Kind kind, Oid dboid, uint64 objid, bool nowait)
Definition: pgstat_shmem.c:684
void pgstat_drop_transactional(PgStat_Kind kind, Oid dboid, uint64 objid)
Definition: pgstat_xact.c:384
void pgstat_create_transactional(PgStat_Kind kind, Oid dboid, uint64 objid)
Definition: pgstat_xact.c:361
PgStat_SubXactStatus * pgstat_get_xact_stack_level(int nest_level)
Definition: pgstat_xact.c:238
#define InvalidOid
Definition: postgres_ext.h:37
unsigned int Oid
Definition: postgres_ext.h:32
#define RelationGetRelid(relation)
Definition: rel.h:505
PgStat_StatTabEntry stats
PgStatShared_Common * shared_stats
PgStatShared_HashEntry * shared_entry
PgStat_Counter tuples_updated
Definition: pgstat.h:368
PgStat_Counter tuples_inserted
Definition: pgstat.h:367
PgStat_Counter tuples_returned
Definition: pgstat.h:365
PgStat_Counter blocks_hit
Definition: pgstat.h:364
PgStat_Counter blocks_fetched
Definition: pgstat.h:363
PgStat_Counter tuples_deleted
Definition: pgstat.h:369
PgStat_Counter tuples_fetched
Definition: pgstat.h:366
PgStat_Counter vacuum_count
Definition: pgstat.h:461
PgStat_Counter tuples_fetched
Definition: pgstat.h:444
PgStat_Counter ins_since_vacuum
Definition: pgstat.h:455
PgStat_Counter blocks_hit
Definition: pgstat.h:458
PgStat_Counter mod_since_analyze
Definition: pgstat.h:454
TimestampTz last_autovacuum_time
Definition: pgstat.h:462
PgStat_Counter analyze_count
Definition: pgstat.h:465
PgStat_Counter tuples_deleted
Definition: pgstat.h:448
PgStat_Counter tuples_hot_updated
Definition: pgstat.h:449
PgStat_Counter tuples_updated
Definition: pgstat.h:447
PgStat_Counter live_tuples
Definition: pgstat.h:452
PgStat_Counter numscans
Definition: pgstat.h:440
PgStat_Counter autovacuum_count
Definition: pgstat.h:463
TimestampTz last_analyze_time
Definition: pgstat.h:464
PgStat_Counter dead_tuples
Definition: pgstat.h:453
PgStat_Counter autoanalyze_count
Definition: pgstat.h:467
PgStat_Counter blocks_fetched
Definition: pgstat.h:457
PgStat_Counter tuples_returned
Definition: pgstat.h:443
TimestampTz last_autoanalyze_time
Definition: pgstat.h:466
TimestampTz lastscan
Definition: pgstat.h:441
PgStat_Counter tuples_inserted
Definition: pgstat.h:446
PgStat_Counter tuples_newpage_updated
Definition: pgstat.h:450
TimestampTz last_vacuum_time
Definition: pgstat.h:460
PgStat_TableXactStatus * first
PgStat_Counter blocks_hit
Definition: pgstat.h:156
PgStat_Counter numscans
Definition: pgstat.h:139
PgStat_Counter tuples_hot_updated
Definition: pgstat.h:147
PgStat_Counter tuples_returned
Definition: pgstat.h:141
PgStat_Counter tuples_inserted
Definition: pgstat.h:144
PgStat_Counter delta_live_tuples
Definition: pgstat.h:151
PgStat_Counter changed_tuples
Definition: pgstat.h:153
PgStat_Counter tuples_updated
Definition: pgstat.h:145
PgStat_Counter blocks_fetched
Definition: pgstat.h:155
PgStat_Counter tuples_fetched
Definition: pgstat.h:142
PgStat_Counter tuples_newpage_updated
Definition: pgstat.h:148
PgStat_Counter delta_dead_tuples
Definition: pgstat.h:152
PgStat_Counter tuples_deleted
Definition: pgstat.h:146
PgStat_TableCounts counts
Definition: pgstat.h:179
struct PgStat_TableXactStatus * trans
Definition: pgstat.h:178
Relation relation
Definition: pgstat.h:180
PgStat_Counter tuples_inserted
Definition: pgstat.h:189
PgStat_Counter tuples_updated
Definition: pgstat.h:190
PgStat_Counter tuples_deleted
Definition: pgstat.h:191
bool pgstat_enabled
Definition: rel.h:253
Form_pg_class rd_rel
Definition: rel.h:111
struct PgStat_TableStatus * pgstat_info
Definition: rel.h:255
PgStat_Counter deleted_pre_truncdrop
PgStat_Counter inserted_pre_truncdrop
PgStat_Counter updated_pre_truncdrop
PgStat_Counter tuples_deleted
PgStat_Counter tuples_inserted
PgStat_Counter tuples_updated
void RegisterTwoPhaseRecord(TwoPhaseRmgrId rmid, uint16 info, const void *data, uint32 len)
Definition: twophase.c:1280
#define TWOPHASE_RM_PGSTAT_ID
Definition: twophase_rmgr.h:26
int GetCurrentTransactionNestLevel(void)
Definition: xact.c:928
TimestampTz GetCurrentTransactionStopTimestamp(void)
Definition: xact.c:890
static zic_t trans[TZ_MAX_LEAPS]
Definition: zic.c:402