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