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 relation statistics. 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-2026, 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
211 PgStat_Counter deadtuples, TimestampTz starttime)
212{
213 PgStat_EntryRef *entry_ref;
214 PgStatShared_Relation *shtabentry;
215 PgStat_StatTabEntry *tabentry;
216 Oid dboid = (rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId);
217 TimestampTz ts;
218 PgStat_Counter elapsedtime;
219
221 return;
222
223 /* Store the data in the table's hash table entry. */
224 ts = GetCurrentTimestamp();
225 elapsedtime = TimestampDifferenceMilliseconds(starttime, ts);
226
227 /* block acquiring lock for the same reason as pgstat_report_autovac() */
229 RelationGetRelid(rel), false);
230
231 shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats;
232 tabentry = &shtabentry->stats;
233
234 tabentry->live_tuples = livetuples;
235 tabentry->dead_tuples = deadtuples;
236
237 /*
238 * It is quite possible that a non-aggressive VACUUM ended up skipping
239 * various pages, however, we'll zero the insert counter here regardless.
240 * It's currently used only to track when we need to perform an "insert"
241 * autovacuum, which are mainly intended to freeze newly inserted tuples.
242 * Zeroing this may just mean we'll not try to vacuum the table again
243 * until enough tuples have been inserted to trigger another insert
244 * autovacuum. An anti-wraparound autovacuum will catch any persistent
245 * stragglers.
246 */
247 tabentry->ins_since_vacuum = 0;
248
250 {
251 tabentry->last_autovacuum_time = ts;
252 tabentry->autovacuum_count++;
253 tabentry->total_autovacuum_time += elapsedtime;
254 }
255 else
256 {
257 tabentry->last_vacuum_time = ts;
258 tabentry->vacuum_count++;
259 tabentry->total_vacuum_time += elapsedtime;
260 }
261
262 pgstat_unlock_entry(entry_ref);
263
264 /*
265 * Flush IO statistics now. pgstat_report_stat() will flush IO stats,
266 * however this will not be called until after an entire autovacuum cycle
267 * is done -- which will likely vacuum many relations -- or until the
268 * VACUUM command has processed all tables and committed.
269 */
270 pgstat_flush_io(false);
272}
273
274/*
275 * Report that the table was just analyzed and flush IO statistics.
276 *
277 * Caller must provide new live- and dead-tuples estimates, as well as a
278 * flag indicating whether to reset the mod_since_analyze counter.
279 */
280void
282 PgStat_Counter livetuples, PgStat_Counter deadtuples,
283 bool resetcounter, TimestampTz starttime)
284{
285 PgStat_EntryRef *entry_ref;
286 PgStatShared_Relation *shtabentry;
287 PgStat_StatTabEntry *tabentry;
288 Oid dboid = (rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId);
289 TimestampTz ts;
290 PgStat_Counter elapsedtime;
291
293 return;
294
295 /*
296 * Unlike VACUUM, ANALYZE might be running inside a transaction that has
297 * already inserted and/or deleted rows in the target table. ANALYZE will
298 * have counted such rows as live or dead respectively. Because we will
299 * report our counts of such rows at transaction end, we should subtract
300 * off these counts from the update we're making now, else they'll be
301 * double-counted after commit. (This approach also ensures that the
302 * shared stats entry ends up with the right numbers if we abort instead
303 * of committing.)
304 *
305 * Waste no time on partitioned tables, though.
306 */
308 rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
309 {
311
312 for (trans = rel->pgstat_info->trans; trans; trans = trans->upper)
313 {
314 livetuples -= trans->tuples_inserted - trans->tuples_deleted;
315 deadtuples -= trans->tuples_updated + trans->tuples_deleted;
316 }
317 /* count stuff inserted by already-aborted subxacts, too */
318 deadtuples -= rel->pgstat_info->counts.delta_dead_tuples;
319 /* Since ANALYZE's counts are estimates, we could have underflowed */
320 livetuples = Max(livetuples, 0);
321 deadtuples = Max(deadtuples, 0);
322 }
323
324 /* Store the data in the table's hash table entry. */
325 ts = GetCurrentTimestamp();
326 elapsedtime = TimestampDifferenceMilliseconds(starttime, ts);
327
328 /* block acquiring lock for the same reason as pgstat_report_autovac() */
330 RelationGetRelid(rel),
331 false);
332 /* can't get dropped while accessed */
333 Assert(entry_ref != NULL && entry_ref->shared_stats != NULL);
334
335 shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats;
336 tabentry = &shtabentry->stats;
337
338 tabentry->live_tuples = livetuples;
339 tabentry->dead_tuples = deadtuples;
340
341 /*
342 * If commanded, reset mod_since_analyze to zero. This forgets any
343 * changes that were committed while the ANALYZE was in progress, but we
344 * have no good way to estimate how many of those there were.
345 */
346 if (resetcounter)
347 tabentry->mod_since_analyze = 0;
348
350 {
351 tabentry->last_autoanalyze_time = ts;
352 tabentry->autoanalyze_count++;
353 tabentry->total_autoanalyze_time += elapsedtime;
354 }
355 else
356 {
357 tabentry->last_analyze_time = ts;
358 tabentry->analyze_count++;
359 tabentry->total_analyze_time += elapsedtime;
360 }
361
362 pgstat_unlock_entry(entry_ref);
363
364 /* see pgstat_report_vacuum() */
365 pgstat_flush_io(false);
367}
368
369/*
370 * count a tuple insertion of n tuples
371 */
372void
374{
376 {
377 PgStat_TableStatus *pgstat_info = rel->pgstat_info;
378
379 ensure_tabstat_xact_level(pgstat_info);
380 pgstat_info->trans->tuples_inserted += n;
381 }
382}
383
384/*
385 * count a tuple update
386 */
387void
388pgstat_count_heap_update(Relation rel, bool hot, bool newpage)
389{
390 Assert(!(hot && newpage));
391
393 {
394 PgStat_TableStatus *pgstat_info = rel->pgstat_info;
395
396 ensure_tabstat_xact_level(pgstat_info);
397 pgstat_info->trans->tuples_updated++;
398
399 /*
400 * tuples_hot_updated and tuples_newpage_updated counters are
401 * nontransactional, so just advance them
402 */
403 if (hot)
404 pgstat_info->counts.tuples_hot_updated++;
405 else if (newpage)
406 pgstat_info->counts.tuples_newpage_updated++;
407 }
408}
409
410/*
411 * count a tuple deletion
412 */
413void
415{
417 {
418 PgStat_TableStatus *pgstat_info = rel->pgstat_info;
419
420 ensure_tabstat_xact_level(pgstat_info);
421 pgstat_info->trans->tuples_deleted++;
422 }
423}
424
425/*
426 * update tuple counters due to truncate
427 */
428void
430{
432 {
433 PgStat_TableStatus *pgstat_info = rel->pgstat_info;
434
435 ensure_tabstat_xact_level(pgstat_info);
436 save_truncdrop_counters(pgstat_info->trans, false);
437 pgstat_info->trans->tuples_inserted = 0;
438 pgstat_info->trans->tuples_updated = 0;
439 pgstat_info->trans->tuples_deleted = 0;
440 }
441}
442
443/*
444 * update dead-tuples count
445 *
446 * The semantics of this are that we are reporting the nontransactional
447 * recovery of "delta" dead tuples; so delta_dead_tuples decreases
448 * rather than increasing, and the change goes straight into the per-table
449 * counter, not into transactional state.
450 */
451void
453{
455 {
456 PgStat_TableStatus *pgstat_info = rel->pgstat_info;
457
458 pgstat_info->counts.delta_dead_tuples -= delta;
459 }
460}
461
462/*
463 * Support function for the SQL-callable pgstat* functions. Returns
464 * the collected statistics for one table or NULL. NULL doesn't mean
465 * that the table doesn't exist, just that there are no statistics, so the
466 * caller is better off to report ZERO instead.
467 */
470{
472}
473
474/*
475 * More efficient version of pgstat_fetch_stat_tabentry(), allowing to specify
476 * whether the to-be-accessed table is a shared relation or not.
477 */
480{
481 Oid dboid = (shared ? InvalidOid : MyDatabaseId);
482
483 return (PgStat_StatTabEntry *)
485}
486
487/*
488 * find any existing PgStat_TableStatus entry for rel
489 *
490 * Find any existing PgStat_TableStatus entry for rel_id in the current
491 * database. If not found, try finding from shared tables.
492 *
493 * If an entry is found, copy it and increment the copy's counters with their
494 * subtransaction counterparts, then return the copy. The caller may need to
495 * pfree() the copy.
496 *
497 * If no entry found, return NULL, don't create a new one.
498 */
501{
502 PgStat_EntryRef *entry_ref;
504 PgStat_TableStatus *tabentry = NULL;
505 PgStat_TableStatus *tablestatus = NULL;
506
508 if (!entry_ref)
509 {
511 if (!entry_ref)
512 return tablestatus;
513 }
514
515 tabentry = (PgStat_TableStatus *) entry_ref->pending;
516 tablestatus = palloc_object(PgStat_TableStatus);
517 *tablestatus = *tabentry;
518
519 /*
520 * Reset tablestatus->trans in the copy of PgStat_TableStatus as it may
521 * point to a shared memory area. Its data is saved below, so removing it
522 * does not matter.
523 */
524 tablestatus->trans = NULL;
525
526 /*
527 * Live subtransaction counts are not included yet. This is not a hot
528 * code path so reconcile tuples_inserted, tuples_updated and
529 * tuples_deleted even if the caller may not be interested in this data.
530 */
531 for (trans = tabentry->trans; trans != NULL; trans = trans->upper)
532 {
533 tablestatus->counts.tuples_inserted += trans->tuples_inserted;
534 tablestatus->counts.tuples_updated += trans->tuples_updated;
535 tablestatus->counts.tuples_deleted += trans->tuples_deleted;
536 }
537
538 return tablestatus;
539}
540
541/*
542 * Perform relation stats specific end-of-transaction work. Helper for
543 * AtEOXact_PgStat.
544 *
545 * Transfer transactional insert/update counts into the base tabstat entries.
546 * We don't bother to free any of the transactional state, since it's all in
547 * TopTransactionContext and will go away anyway.
548 */
549void
551{
553
554 for (trans = xact_state->first; trans != NULL; trans = trans->next)
555 {
556 PgStat_TableStatus *tabstat;
557
558 Assert(trans->nest_level == 1);
559 Assert(trans->upper == NULL);
560 tabstat = trans->parent;
561 Assert(tabstat->trans == trans);
562 /* restore pre-truncate/drop stats (if any) in case of aborted xact */
563 if (!isCommit)
565 /* count attempted actions regardless of commit/abort */
566 tabstat->counts.tuples_inserted += trans->tuples_inserted;
567 tabstat->counts.tuples_updated += trans->tuples_updated;
568 tabstat->counts.tuples_deleted += trans->tuples_deleted;
569 if (isCommit)
570 {
571 tabstat->counts.truncdropped = trans->truncdropped;
572 if (trans->truncdropped)
573 {
574 /* forget live/dead stats seen by backend thus far */
575 tabstat->counts.delta_live_tuples = 0;
576 tabstat->counts.delta_dead_tuples = 0;
577 }
578 /* insert adds a live tuple, delete removes one */
579 tabstat->counts.delta_live_tuples +=
580 trans->tuples_inserted - trans->tuples_deleted;
581 /* update and delete each create a dead tuple */
582 tabstat->counts.delta_dead_tuples +=
583 trans->tuples_updated + trans->tuples_deleted;
584 /* insert, update, delete each count as one change event */
585 tabstat->counts.changed_tuples +=
586 trans->tuples_inserted + trans->tuples_updated +
587 trans->tuples_deleted;
588 }
589 else
590 {
591 /* inserted tuples are dead, deleted tuples are unaffected */
592 tabstat->counts.delta_dead_tuples +=
593 trans->tuples_inserted + trans->tuples_updated;
594 /* an aborted xact generates no changed_tuple events */
595 }
596 tabstat->trans = NULL;
597 }
598}
599
600/*
601 * Perform relation stats specific end-of-sub-transaction work. Helper for
602 * AtEOSubXact_PgStat.
603 *
604 * Transfer transactional insert/update counts into the next higher
605 * subtransaction state.
606 */
607void
608AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth)
609{
611 PgStat_TableXactStatus *next_trans;
612
613 for (trans = xact_state->first; trans != NULL; trans = next_trans)
614 {
615 PgStat_TableStatus *tabstat;
616
617 next_trans = trans->next;
618 Assert(trans->nest_level == nestDepth);
619 tabstat = trans->parent;
620 Assert(tabstat->trans == trans);
621
622 if (isCommit)
623 {
624 if (trans->upper && trans->upper->nest_level == nestDepth - 1)
625 {
626 if (trans->truncdropped)
627 {
628 /* propagate the truncate/drop status one level up */
629 save_truncdrop_counters(trans->upper, false);
630 /* replace upper xact stats with ours */
631 trans->upper->tuples_inserted = trans->tuples_inserted;
632 trans->upper->tuples_updated = trans->tuples_updated;
633 trans->upper->tuples_deleted = trans->tuples_deleted;
634 }
635 else
636 {
637 trans->upper->tuples_inserted += trans->tuples_inserted;
638 trans->upper->tuples_updated += trans->tuples_updated;
639 trans->upper->tuples_deleted += trans->tuples_deleted;
640 }
641 tabstat->trans = trans->upper;
642 pfree(trans);
643 }
644 else
645 {
646 /*
647 * When there isn't an immediate parent state, we can just
648 * reuse the record instead of going through a palloc/pfree
649 * pushup (this works since it's all in TopTransactionContext
650 * anyway). We have to re-link it into the parent level,
651 * though, and that might mean pushing a new entry into the
652 * pgStatXactStack.
653 */
654 PgStat_SubXactStatus *upper_xact_state;
655
656 upper_xact_state = pgstat_get_xact_stack_level(nestDepth - 1);
657 trans->next = upper_xact_state->first;
658 upper_xact_state->first = trans;
659 trans->nest_level = nestDepth - 1;
660 }
661 }
662 else
663 {
664 /*
665 * On abort, update top-level tabstat counts, then forget the
666 * subtransaction
667 */
668
669 /* first restore values obliterated by truncate/drop */
671 /* count attempted actions regardless of commit/abort */
672 tabstat->counts.tuples_inserted += trans->tuples_inserted;
673 tabstat->counts.tuples_updated += trans->tuples_updated;
674 tabstat->counts.tuples_deleted += trans->tuples_deleted;
675 /* inserted tuples are dead, deleted tuples are unaffected */
676 tabstat->counts.delta_dead_tuples +=
677 trans->tuples_inserted + trans->tuples_updated;
678 tabstat->trans = trans->upper;
679 pfree(trans);
680 }
681 }
682}
683
684/*
685 * Generate 2PC records for all the pending transaction-dependent relation
686 * stats.
687 */
688void
690{
692
693 for (trans = xact_state->first; trans != NULL; trans = trans->next)
694 {
697
698 Assert(trans->nest_level == 1);
699 Assert(trans->upper == NULL);
700 tabstat = trans->parent;
701 Assert(tabstat->trans == trans);
702
703 record.tuples_inserted = trans->tuples_inserted;
704 record.tuples_updated = trans->tuples_updated;
705 record.tuples_deleted = trans->tuples_deleted;
706 record.inserted_pre_truncdrop = trans->inserted_pre_truncdrop;
707 record.updated_pre_truncdrop = trans->updated_pre_truncdrop;
708 record.deleted_pre_truncdrop = trans->deleted_pre_truncdrop;
709 record.id = tabstat->id;
710 record.shared = tabstat->shared;
711 record.truncdropped = trans->truncdropped;
712
714 &record, sizeof(TwoPhasePgStatRecord));
715 }
716}
717
718/*
719 * All we need do here is unlink the transaction stats state from the
720 * nontransactional state. The nontransactional action counts will be
721 * reported to the stats system immediately, while the effects on live and
722 * dead tuple counts are preserved in the 2PC state file.
723 *
724 * Note: AtEOXact_PgStat_Relations is not called during PREPARE.
725 */
726void
728{
730
731 for (trans = xact_state->first; trans != NULL; trans = trans->next)
732 {
733 PgStat_TableStatus *tabstat;
734
735 tabstat = trans->parent;
736 tabstat->trans = NULL;
737 }
738}
739
740/*
741 * 2PC processing routine for COMMIT PREPARED case.
742 *
743 * Load the saved counts into our local pgstats state.
744 */
745void
747 void *recdata, uint32 len)
748{
750 PgStat_TableStatus *pgstat_info;
751
752 /* Find or create a tabstat entry for the rel */
753 pgstat_info = pgstat_prep_relation_pending(rec->id, rec->shared);
754
755 /* Same math as in AtEOXact_PgStat, commit case */
756 pgstat_info->counts.tuples_inserted += rec->tuples_inserted;
757 pgstat_info->counts.tuples_updated += rec->tuples_updated;
758 pgstat_info->counts.tuples_deleted += rec->tuples_deleted;
759 pgstat_info->counts.truncdropped = rec->truncdropped;
760 if (rec->truncdropped)
761 {
762 /* forget live/dead stats seen by backend thus far */
763 pgstat_info->counts.delta_live_tuples = 0;
764 pgstat_info->counts.delta_dead_tuples = 0;
765 }
766 pgstat_info->counts.delta_live_tuples +=
768 pgstat_info->counts.delta_dead_tuples +=
769 rec->tuples_updated + rec->tuples_deleted;
770 pgstat_info->counts.changed_tuples +=
771 rec->tuples_inserted + rec->tuples_updated +
772 rec->tuples_deleted;
773}
774
775/*
776 * 2PC processing routine for ROLLBACK PREPARED case.
777 *
778 * Load the saved counts into our local pgstats state, but treat them
779 * as aborted.
780 */
781void
783 void *recdata, uint32 len)
784{
786 PgStat_TableStatus *pgstat_info;
787
788 /* Find or create a tabstat entry for the rel */
789 pgstat_info = pgstat_prep_relation_pending(rec->id, rec->shared);
790
791 /* Same math as in AtEOXact_PgStat, abort case */
792 if (rec->truncdropped)
793 {
797 }
798 pgstat_info->counts.tuples_inserted += rec->tuples_inserted;
799 pgstat_info->counts.tuples_updated += rec->tuples_updated;
800 pgstat_info->counts.tuples_deleted += rec->tuples_deleted;
801 pgstat_info->counts.delta_dead_tuples +=
803}
804
805/*
806 * Flush out pending stats for the entry
807 *
808 * If nowait is true and the lock could not be immediately acquired, returns
809 * false without flushing the entry. Otherwise returns true.
810 *
811 * Some of the stats are copied to the corresponding pending database stats
812 * entry when successfully flushing.
813 */
814bool
816{
817 Oid dboid;
818 PgStat_TableStatus *lstats; /* pending stats entry */
819 PgStatShared_Relation *shtabstats;
820 PgStat_StatTabEntry *tabentry; /* table entry of shared stats */
821 PgStat_StatDBEntry *dbentry; /* pending database entry */
822
823 dboid = entry_ref->shared_entry->key.dboid;
824 lstats = (PgStat_TableStatus *) entry_ref->pending;
825 shtabstats = (PgStatShared_Relation *) entry_ref->shared_stats;
826
827 /*
828 * Ignore entries that didn't accumulate any actual counts, such as
829 * indexes that were opened by the planner but not used.
830 */
831 if (pg_memory_is_all_zeros(&lstats->counts,
832 sizeof(struct PgStat_TableCounts)))
833 return true;
834
835 if (!pgstat_lock_entry(entry_ref, nowait))
836 return false;
837
838 /* add the values to the shared entry. */
839 tabentry = &shtabstats->stats;
840
841 tabentry->numscans += lstats->counts.numscans;
842 if (lstats->counts.numscans)
843 {
845
846 if (t > tabentry->lastscan)
847 tabentry->lastscan = t;
848 }
849 tabentry->tuples_returned += lstats->counts.tuples_returned;
850 tabentry->tuples_fetched += lstats->counts.tuples_fetched;
851 tabentry->tuples_inserted += lstats->counts.tuples_inserted;
852 tabentry->tuples_updated += lstats->counts.tuples_updated;
853 tabentry->tuples_deleted += lstats->counts.tuples_deleted;
854 tabentry->tuples_hot_updated += lstats->counts.tuples_hot_updated;
856
857 /*
858 * If table was truncated/dropped, first reset the live/dead counters.
859 */
860 if (lstats->counts.truncdropped)
861 {
862 tabentry->live_tuples = 0;
863 tabentry->dead_tuples = 0;
864 tabentry->ins_since_vacuum = 0;
865 }
866
867 tabentry->live_tuples += lstats->counts.delta_live_tuples;
868 tabentry->dead_tuples += lstats->counts.delta_dead_tuples;
869 tabentry->mod_since_analyze += lstats->counts.changed_tuples;
870
871 /*
872 * Using tuples_inserted to update ins_since_vacuum does mean that we'll
873 * track aborted inserts too. This isn't ideal, but otherwise probably
874 * not worth adding an extra field for. It may just amount to autovacuums
875 * triggering for inserts more often than they maybe should, which is
876 * probably not going to be common enough to be too concerned about here.
877 */
878 tabentry->ins_since_vacuum += lstats->counts.tuples_inserted;
879
880 tabentry->blocks_fetched += lstats->counts.blocks_fetched;
881 tabentry->blocks_hit += lstats->counts.blocks_hit;
882
883 /* Clamp live_tuples in case of negative delta_live_tuples */
884 tabentry->live_tuples = Max(tabentry->live_tuples, 0);
885 /* Likewise for dead_tuples */
886 tabentry->dead_tuples = Max(tabentry->dead_tuples, 0);
887
888 pgstat_unlock_entry(entry_ref);
889
890 /* The entry was successfully flushed, add the same to database stats */
891 dbentry = pgstat_prep_database_pending(dboid);
892 dbentry->tuples_returned += lstats->counts.tuples_returned;
893 dbentry->tuples_fetched += lstats->counts.tuples_fetched;
894 dbentry->tuples_inserted += lstats->counts.tuples_inserted;
895 dbentry->tuples_updated += lstats->counts.tuples_updated;
896 dbentry->tuples_deleted += lstats->counts.tuples_deleted;
897 dbentry->blocks_fetched += lstats->counts.blocks_fetched;
898 dbentry->blocks_hit += lstats->counts.blocks_hit;
899
900 return true;
901}
902
903void
905{
906 PgStat_TableStatus *pending = (PgStat_TableStatus *) entry_ref->pending;
907
908 if (pending->relation)
910}
911
912void
914{
915 ((PgStatShared_Relation *) header)->stats.stat_reset_time = ts;
916}
917
918/*
919 * Find or create a PgStat_TableStatus entry for rel. New entry is created and
920 * initialized if not exists.
921 */
922static PgStat_TableStatus *
923pgstat_prep_relation_pending(Oid rel_id, bool isshared)
924{
925 PgStat_EntryRef *entry_ref;
926 PgStat_TableStatus *pending;
927
929 isshared ? InvalidOid : MyDatabaseId,
930 rel_id, NULL);
931 pending = entry_ref->pending;
932 pending->id = rel_id;
933 pending->shared = isshared;
934
935 return pending;
936}
937
938/*
939 * add a new (sub)transaction state record
940 */
941static void
942add_tabstat_xact_level(PgStat_TableStatus *pgstat_info, int nest_level)
943{
944 PgStat_SubXactStatus *xact_state;
946
947 /*
948 * If this is the first rel to be modified at the current nest level, we
949 * first have to push a transaction stack entry.
950 */
951 xact_state = pgstat_get_xact_stack_level(nest_level);
952
953 /* Now make a per-table stack entry */
956 sizeof(PgStat_TableXactStatus));
957 trans->nest_level = nest_level;
958 trans->upper = pgstat_info->trans;
959 trans->parent = pgstat_info;
960 trans->next = xact_state->first;
961 xact_state->first = trans;
962 pgstat_info->trans = trans;
963}
964
965/*
966 * Add a new (sub)transaction record if needed.
967 */
968static void
970{
971 int nest_level = GetCurrentTransactionNestLevel();
972
973 if (pgstat_info->trans == NULL ||
974 pgstat_info->trans->nest_level != nest_level)
975 add_tabstat_xact_level(pgstat_info, nest_level);
976}
977
978/*
979 * Whenever a table is truncated/dropped, we save its i/u/d counters so that
980 * they can be cleared, and if the (sub)xact that executed the truncate/drop
981 * later aborts, the counters can be restored to the saved (pre-truncate/drop)
982 * values.
983 *
984 * Note that for truncate we do this on the first truncate in any particular
985 * subxact level only.
986 */
987static void
989{
990 if (!trans->truncdropped || is_drop)
991 {
992 trans->inserted_pre_truncdrop = trans->tuples_inserted;
993 trans->updated_pre_truncdrop = trans->tuples_updated;
994 trans->deleted_pre_truncdrop = trans->tuples_deleted;
995 trans->truncdropped = true;
996 }
997}
998
999/*
1000 * restore counters when a truncate aborts
1001 */
1002static void
1004{
1005 if (trans->truncdropped)
1006 {
1007 trans->tuples_inserted = trans->inserted_pre_truncdrop;
1008 trans->tuples_updated = trans->updated_pre_truncdrop;
1009 trans->tuples_deleted = trans->deleted_pre_truncdrop;
1010 }
1011}
long TimestampDifferenceMilliseconds(TimestampTz start_time, TimestampTz stop_time)
Definition: timestamp.c:1757
TimestampTz GetCurrentTimestamp(void)
Definition: timestamp.c:1645
#define PG_USED_FOR_ASSERTS_ONLY
Definition: c.h:229
#define Max(x, y)
Definition: c.h:989
uint16_t uint16
Definition: c.h:551
uint32_t uint32
Definition: c.h:552
bool IsSharedRelation(Oid relationId)
Definition: catalog.c:304
int64 TimestampTz
Definition: timestamp.h:39
#define palloc_object(type)
Definition: fe_memutils.h:74
Oid MyDatabaseId
Definition: globals.c:94
Assert(PointerIsAligned(start, uint64))
if(TABLE==NULL||TABLE_index==NULL)
Definition: isn.c:81
void * MemoryContextAllocZero(MemoryContext context, Size size)
Definition: mcxt.c:1266
MemoryContext TopTransactionContext
Definition: mcxt.c:171
void pfree(void *pointer)
Definition: mcxt.c:1616
static bool pg_memory_is_all_zeros(const void *ptr, size_t len)
Definition: memutils.h:219
#define AmAutoVacuumWorkerProcess()
Definition: miscadmin.h:383
const void size_t len
PgStat_EntryRef * pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry)
Definition: pgstat.c:1275
bool pgstat_track_counts
Definition: pgstat.c:203
PgStat_EntryRef * pgstat_fetch_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
Definition: pgstat.c:1313
void * pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid)
Definition: pgstat.c:944
#define pgstat_should_count_relation(rel)
Definition: pgstat.h:684
int64 PgStat_Counter
Definition: pgstat.h:67
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:175
#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_report_vacuum(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, TimestampTz starttime)
void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Counter deadtuples, bool resetcounter, TimestampTz starttime)
static PgStat_TableStatus * pgstat_prep_relation_pending(Oid rel_id, bool isshared)
void pgstat_assoc_relation(Relation rel)
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)
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_relation_reset_timestamp_cb(PgStatShared_Common *header, TimestampTz ts)
void PostPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
void pgstat_twophase_postcommit(FullTransactionId fxid, uint16 info, void *recdata, uint32 len)
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_twophase_postabort(FullTransactionId fxid, uint16 info, void *recdata, uint32 len)
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:720
bool pgstat_lock_entry(PgStat_EntryRef *entry_ref, bool nowait)
Definition: pgstat_shmem.c:690
PgStat_EntryRef * pgstat_get_entry_ref_locked(PgStat_Kind kind, Oid dboid, uint64 objid, bool nowait)
Definition: pgstat_shmem.c:729
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:515
PgStat_StatTabEntry stats
PgStatShared_Common * shared_stats
PgStatShared_HashEntry * shared_entry
PgStat_Counter tuples_updated
Definition: pgstat.h:354
PgStat_Counter tuples_inserted
Definition: pgstat.h:353
PgStat_Counter tuples_returned
Definition: pgstat.h:351
PgStat_Counter blocks_hit
Definition: pgstat.h:350
PgStat_Counter blocks_fetched
Definition: pgstat.h:349
PgStat_Counter tuples_deleted
Definition: pgstat.h:355
PgStat_Counter tuples_fetched
Definition: pgstat.h:352
PgStat_Counter vacuum_count
Definition: pgstat.h:452
PgStat_Counter tuples_fetched
Definition: pgstat.h:435
PgStat_Counter ins_since_vacuum
Definition: pgstat.h:446
PgStat_Counter blocks_hit
Definition: pgstat.h:449
PgStat_Counter mod_since_analyze
Definition: pgstat.h:445
TimestampTz last_autovacuum_time
Definition: pgstat.h:453
PgStat_Counter analyze_count
Definition: pgstat.h:456
PgStat_Counter tuples_deleted
Definition: pgstat.h:439
PgStat_Counter tuples_hot_updated
Definition: pgstat.h:440
PgStat_Counter tuples_updated
Definition: pgstat.h:438
PgStat_Counter live_tuples
Definition: pgstat.h:443
PgStat_Counter numscans
Definition: pgstat.h:431
PgStat_Counter autovacuum_count
Definition: pgstat.h:454
PgStat_Counter total_autovacuum_time
Definition: pgstat.h:461
PgStat_Counter total_analyze_time
Definition: pgstat.h:462
TimestampTz last_analyze_time
Definition: pgstat.h:455
PgStat_Counter dead_tuples
Definition: pgstat.h:444
PgStat_Counter autoanalyze_count
Definition: pgstat.h:458
PgStat_Counter blocks_fetched
Definition: pgstat.h:448
PgStat_Counter tuples_returned
Definition: pgstat.h:434
TimestampTz last_autoanalyze_time
Definition: pgstat.h:457
PgStat_Counter total_autoanalyze_time
Definition: pgstat.h:463
TimestampTz lastscan
Definition: pgstat.h:432
PgStat_Counter tuples_inserted
Definition: pgstat.h:437
PgStat_Counter total_vacuum_time
Definition: pgstat.h:460
PgStat_Counter tuples_newpage_updated
Definition: pgstat.h:441
TimestampTz last_vacuum_time
Definition: pgstat.h:451
PgStat_TableXactStatus * first
PgStat_Counter blocks_hit
Definition: pgstat.h:158
PgStat_Counter numscans
Definition: pgstat.h:141
PgStat_Counter tuples_hot_updated
Definition: pgstat.h:149
PgStat_Counter tuples_returned
Definition: pgstat.h:143
PgStat_Counter tuples_inserted
Definition: pgstat.h:146
PgStat_Counter delta_live_tuples
Definition: pgstat.h:153
PgStat_Counter changed_tuples
Definition: pgstat.h:155
PgStat_Counter tuples_updated
Definition: pgstat.h:147
PgStat_Counter blocks_fetched
Definition: pgstat.h:157
PgStat_Counter tuples_fetched
Definition: pgstat.h:144
PgStat_Counter tuples_newpage_updated
Definition: pgstat.h:150
PgStat_Counter delta_dead_tuples
Definition: pgstat.h:154
PgStat_Counter tuples_deleted
Definition: pgstat.h:148
PgStat_TableCounts counts
Definition: pgstat.h:181
struct PgStat_TableXactStatus * trans
Definition: pgstat.h:180
Relation relation
Definition: pgstat.h:182
PgStat_Counter tuples_inserted
Definition: pgstat.h:191
PgStat_Counter tuples_updated
Definition: pgstat.h:192
PgStat_Counter tuples_deleted
Definition: pgstat.h:193
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:1271
#define TWOPHASE_RM_PGSTAT_ID
Definition: twophase_rmgr.h:28
int GetCurrentTransactionNestLevel(void)
Definition: xact.c:930
TimestampTz GetCurrentTransactionStopTimestamp(void)
Definition: xact.c:892
static zic_t trans[TZ_MAX_LEAPS]
Definition: zic.c:405