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-2023, 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/partition.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 #include "catalog/catalog.h"
29 
30 
31 /* Record that's written to 2PC state file when pgstat state is persisted */
32 typedef struct TwoPhasePgStatRecord
33 {
34  PgStat_Counter tuples_inserted; /* tuples inserted in xact */
35  PgStat_Counter tuples_updated; /* tuples updated in xact */
36  PgStat_Counter tuples_deleted; /* tuples deleted in xact */
37  /* tuples i/u/d prior to truncate/drop */
41  Oid id; /* table's OID */
42  bool shared; /* is it a shared catalog? */
43  bool truncdropped; /* was the relation truncated/dropped? */
45 
46 
47 static PgStat_TableStatus *pgstat_prep_relation_pending(Oid rel_id, bool isshared);
48 static void add_tabstat_xact_level(PgStat_TableStatus *pgstat_info, int nest_level);
49 static void ensure_tabstat_xact_level(PgStat_TableStatus *pgstat_info);
50 static void save_truncdrop_counters(PgStat_TableXactStatus *trans, bool is_drop);
52 
53 
54 /*
55  * Copy stats between relations. This is used for things like REINDEX
56  * CONCURRENTLY.
57  */
58 void
60 {
61  PgStat_StatTabEntry *srcstats;
62  PgStatShared_Relation *dstshstats;
63  PgStat_EntryRef *dst_ref;
64 
65  srcstats = pgstat_fetch_stat_tabentry_ext(src->rd_rel->relisshared,
66  RelationGetRelid(src));
67  if (!srcstats)
68  return;
69 
71  dst->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
72  RelationGetRelid(dst),
73  false);
74 
75  dstshstats = (PgStatShared_Relation *) dst_ref->shared_stats;
76  dstshstats->stats = *srcstats;
77 
78  pgstat_unlock_entry(dst_ref);
79 }
80 
81 /*
82  * Initialize a relcache entry to count access statistics. Called whenever a
83  * relation is opened.
84  *
85  * We assume that a relcache entry's pgstat_info field is zeroed by relcache.c
86  * when the relcache entry is made; thereafter it is long-lived data.
87  *
88  * This does not create a reference to a stats entry in shared memory, nor
89  * allocate memory for the pending stats. That happens in
90  * pgstat_assoc_relation().
91  */
92 void
94 {
95  char relkind = rel->rd_rel->relkind;
96 
97  /*
98  * We only count stats for relations with storage and partitioned tables
99  */
100  if (!RELKIND_HAS_STORAGE(relkind) && relkind != RELKIND_PARTITIONED_TABLE)
101  {
102  rel->pgstat_enabled = false;
103  rel->pgstat_info = NULL;
104  return;
105  }
106 
107  if (!pgstat_track_counts)
108  {
109  if (rel->pgstat_info)
111 
112  /* We're not counting at all */
113  rel->pgstat_enabled = false;
114  rel->pgstat_info = NULL;
115  return;
116  }
117 
118  rel->pgstat_enabled = true;
119 }
120 
121 /*
122  * Prepare for statistics for this relation to be collected.
123  *
124  * This ensures we have a reference to the stats entry before stats can be
125  * generated. That is important because a relation drop in another connection
126  * could otherwise lead to the stats entry being dropped, which then later
127  * would get recreated when flushing stats.
128  *
129  * This is separate from pgstat_init_relation() as it is not uncommon for
130  * relcache entries to be opened without ever getting stats reported.
131  */
132 void
134 {
135  Assert(rel->pgstat_enabled);
136  Assert(rel->pgstat_info == NULL);
137 
138  /* Else find or make the PgStat_TableStatus entry, and update link */
140  rel->rd_rel->relisshared);
141 
142  /* don't allow link a stats to multiple relcache entries */
143  Assert(rel->pgstat_info->relation == NULL);
144 
145  /* mark this relation as the owner */
146  rel->pgstat_info->relation = rel;
147 }
148 
149 /*
150  * Break the mutual link between a relcache entry and pending stats entry.
151  * This must be called whenever one end of the link is removed.
152  */
153 void
155 {
156  /* remove the link to stats info if any */
157  if (rel->pgstat_info == NULL)
158  return;
159 
160  /* link sanity check */
161  Assert(rel->pgstat_info->relation == rel);
162  rel->pgstat_info->relation = NULL;
163  rel->pgstat_info = NULL;
164 }
165 
166 /*
167  * Ensure that stats are dropped if transaction aborts.
168  */
169 void
171 {
173  rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
174  RelationGetRelid(rel));
175 }
176 
177 /*
178  * Ensure that stats are dropped if transaction commits.
179  */
180 void
182 {
183  int nest_level = GetCurrentTransactionNestLevel();
184  PgStat_TableStatus *pgstat_info;
185 
187  rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
188  RelationGetRelid(rel));
189 
191  return;
192 
193  /*
194  * Transactionally set counters to 0. That ensures that accesses to
195  * pg_stat_xact_all_tables inside the transaction show 0.
196  */
197  pgstat_info = rel->pgstat_info;
198  if (pgstat_info->trans &&
199  pgstat_info->trans->nest_level == nest_level)
200  {
201  save_truncdrop_counters(pgstat_info->trans, true);
202  pgstat_info->trans->tuples_inserted = 0;
203  pgstat_info->trans->tuples_updated = 0;
204  pgstat_info->trans->tuples_deleted = 0;
205  }
206 }
207 
208 /*
209  * Report that the table was just vacuumed and flush IO statistics.
210  */
211 void
212 pgstat_report_vacuum(Oid tableoid, bool shared,
213  PgStat_Counter livetuples, PgStat_Counter deadtuples)
214 {
215  PgStat_EntryRef *entry_ref;
216  PgStatShared_Relation *shtabentry;
217  PgStat_StatTabEntry *tabentry;
218  Oid dboid = (shared ? InvalidOid : MyDatabaseId);
219  TimestampTz ts;
220 
221  if (!pgstat_track_counts)
222  return;
223 
224  /* Store the data in the table's hash table entry. */
225  ts = GetCurrentTimestamp();
226 
227  /* block acquiring lock for the same reason as pgstat_report_autovac() */
229  dboid, tableoid, 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  }
254  else
255  {
256  tabentry->last_vacuum_time = ts;
257  tabentry->vacuum_count++;
258  }
259 
260  pgstat_unlock_entry(entry_ref);
261 
262  /*
263  * Flush IO statistics now. pgstat_report_stat() will flush IO stats,
264  * however this will not be called until after an entire autovacuum cycle
265  * is done -- which will likely vacuum many relations -- or until the
266  * VACUUM command has processed all tables and committed.
267  */
268  pgstat_flush_io(false);
269 }
270 
271 /*
272  * Report that the table was just analyzed and flush IO statistics.
273  *
274  * Caller must provide new live- and dead-tuples estimates, as well as a
275  * flag indicating whether to reset the mod_since_analyze counter.
276  */
277 void
279  PgStat_Counter livetuples, PgStat_Counter deadtuples,
280  bool resetcounter)
281 {
282  PgStat_EntryRef *entry_ref;
283  PgStatShared_Relation *shtabentry;
284  PgStat_StatTabEntry *tabentry;
285  Oid dboid = (rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId);
286 
287  if (!pgstat_track_counts)
288  return;
289 
290  /*
291  * Unlike VACUUM, ANALYZE might be running inside a transaction that has
292  * already inserted and/or deleted rows in the target table. ANALYZE will
293  * have counted such rows as live or dead respectively. Because we will
294  * report our counts of such rows at transaction end, we should subtract
295  * off these counts from the update we're making now, else they'll be
296  * double-counted after commit. (This approach also ensures that the
297  * shared stats entry ends up with the right numbers if we abort instead
298  * of committing.)
299  *
300  * Waste no time on partitioned tables, though.
301  */
302  if (pgstat_should_count_relation(rel) &&
303  rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
304  {
306 
307  for (trans = rel->pgstat_info->trans; trans; trans = trans->upper)
308  {
309  livetuples -= trans->tuples_inserted - trans->tuples_deleted;
310  deadtuples -= trans->tuples_updated + trans->tuples_deleted;
311  }
312  /* count stuff inserted by already-aborted subxacts, too */
313  deadtuples -= rel->pgstat_info->counts.delta_dead_tuples;
314  /* Since ANALYZE's counts are estimates, we could have underflowed */
315  livetuples = Max(livetuples, 0);
316  deadtuples = Max(deadtuples, 0);
317  }
318 
319  /* block acquiring lock for the same reason as pgstat_report_autovac() */
321  RelationGetRelid(rel),
322  false);
323  /* can't get dropped while accessed */
324  Assert(entry_ref != NULL && entry_ref->shared_stats != NULL);
325 
326  shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats;
327  tabentry = &shtabentry->stats;
328 
329  tabentry->live_tuples = livetuples;
330  tabentry->dead_tuples = deadtuples;
331 
332  /*
333  * If commanded, reset mod_since_analyze to zero. This forgets any
334  * changes that were committed while the ANALYZE was in progress, but we
335  * have no good way to estimate how many of those there were.
336  */
337  if (resetcounter)
338  tabentry->mod_since_analyze = 0;
339 
341  {
343  tabentry->autoanalyze_count++;
344  }
345  else
346  {
348  tabentry->analyze_count++;
349  }
350 
351  pgstat_unlock_entry(entry_ref);
352 
353  /* see pgstat_report_vacuum() */
354  pgstat_flush_io(false);
355 }
356 
357 /*
358  * count a tuple insertion of n tuples
359  */
360 void
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  */
375 void
376 pgstat_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  */
401 void
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  */
416 void
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  */
439 void
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 {
459  return pgstat_fetch_stat_tabentry_ext(IsSharedRelation(relid), relid);
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  */
537 void
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  */
595 void
596 AtEOSubXact_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  */
676 void
678 {
680 
681  for (trans = xact_state->first; trans != NULL; trans = trans->next)
682  {
684  TwoPhasePgStatRecord record;
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  */
714 void
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  */
733 void
735  void *recdata, uint32 len)
736 {
737  TwoPhasePgStatRecord *rec = (TwoPhasePgStatRecord *) recdata;
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 +=
755  rec->tuples_inserted - rec->tuples_deleted;
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  */
769 void
771  void *recdata, uint32 len)
772 {
773  TwoPhasePgStatRecord *rec = (TwoPhasePgStatRecord *) recdata;
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 +=
790  rec->tuples_inserted + rec->tuples_updated;
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  */
802 bool
804 {
805  static const PgStat_TableCounts all_zeroes;
806  Oid dboid;
807  PgStat_TableStatus *lstats; /* pending stats entry */
808  PgStatShared_Relation *shtabstats;
809  PgStat_StatTabEntry *tabentry; /* table entry of shared stats */
810  PgStat_StatDBEntry *dbentry; /* pending database entry */
811 
812  dboid = entry_ref->shared_entry->key.dboid;
813  lstats = (PgStat_TableStatus *) entry_ref->pending;
814  shtabstats = (PgStatShared_Relation *) entry_ref->shared_stats;
815 
816  /*
817  * Ignore entries that didn't accumulate any actual counts, such as
818  * indexes that were opened by the planner but not used.
819  */
820  if (memcmp(&lstats->counts, &all_zeroes,
821  sizeof(PgStat_TableCounts)) == 0)
822  {
823  return true;
824  }
825 
826  if (!pgstat_lock_entry(entry_ref, nowait))
827  return false;
828 
829  /* add the values to the shared entry. */
830  tabentry = &shtabstats->stats;
831 
832  tabentry->numscans += lstats->counts.numscans;
833  if (lstats->counts.numscans)
834  {
836 
837  if (t > tabentry->lastscan)
838  tabentry->lastscan = t;
839  }
840  tabentry->tuples_returned += lstats->counts.tuples_returned;
841  tabentry->tuples_fetched += lstats->counts.tuples_fetched;
842  tabentry->tuples_inserted += lstats->counts.tuples_inserted;
843  tabentry->tuples_updated += lstats->counts.tuples_updated;
844  tabentry->tuples_deleted += lstats->counts.tuples_deleted;
845  tabentry->tuples_hot_updated += lstats->counts.tuples_hot_updated;
847 
848  /*
849  * If table was truncated/dropped, first reset the live/dead counters.
850  */
851  if (lstats->counts.truncdropped)
852  {
853  tabentry->live_tuples = 0;
854  tabentry->dead_tuples = 0;
855  tabentry->ins_since_vacuum = 0;
856  }
857 
858  tabentry->live_tuples += lstats->counts.delta_live_tuples;
859  tabentry->dead_tuples += lstats->counts.delta_dead_tuples;
860  tabentry->mod_since_analyze += lstats->counts.changed_tuples;
861  tabentry->ins_since_vacuum += lstats->counts.tuples_inserted;
862  tabentry->blocks_fetched += lstats->counts.blocks_fetched;
863  tabentry->blocks_hit += lstats->counts.blocks_hit;
864 
865  /* Clamp live_tuples in case of negative delta_live_tuples */
866  tabentry->live_tuples = Max(tabentry->live_tuples, 0);
867  /* Likewise for dead_tuples */
868  tabentry->dead_tuples = Max(tabentry->dead_tuples, 0);
869 
870  pgstat_unlock_entry(entry_ref);
871 
872  /* The entry was successfully flushed, add the same to database stats */
873  dbentry = pgstat_prep_database_pending(dboid);
874  dbentry->tuples_returned += lstats->counts.tuples_returned;
875  dbentry->tuples_fetched += lstats->counts.tuples_fetched;
876  dbentry->tuples_inserted += lstats->counts.tuples_inserted;
877  dbentry->tuples_updated += lstats->counts.tuples_updated;
878  dbentry->tuples_deleted += lstats->counts.tuples_deleted;
879  dbentry->blocks_fetched += lstats->counts.blocks_fetched;
880  dbentry->blocks_hit += lstats->counts.blocks_hit;
881 
882  return true;
883 }
884 
885 void
887 {
888  PgStat_TableStatus *pending = (PgStat_TableStatus *) entry_ref->pending;
889 
890  if (pending->relation)
892 }
893 
894 /*
895  * Find or create a PgStat_TableStatus entry for rel. New entry is created and
896  * initialized if not exists.
897  */
898 static PgStat_TableStatus *
899 pgstat_prep_relation_pending(Oid rel_id, bool isshared)
900 {
901  PgStat_EntryRef *entry_ref;
902  PgStat_TableStatus *pending;
903 
905  isshared ? InvalidOid : MyDatabaseId,
906  rel_id, NULL);
907  pending = entry_ref->pending;
908  pending->id = rel_id;
909  pending->shared = isshared;
910 
911  return pending;
912 }
913 
914 /*
915  * add a new (sub)transaction state record
916  */
917 static void
918 add_tabstat_xact_level(PgStat_TableStatus *pgstat_info, int nest_level)
919 {
920  PgStat_SubXactStatus *xact_state;
922 
923  /*
924  * If this is the first rel to be modified at the current nest level, we
925  * first have to push a transaction stack entry.
926  */
927  xact_state = pgstat_get_xact_stack_level(nest_level);
928 
929  /* Now make a per-table stack entry */
932  sizeof(PgStat_TableXactStatus));
933  trans->nest_level = nest_level;
934  trans->upper = pgstat_info->trans;
935  trans->parent = pgstat_info;
936  trans->next = xact_state->first;
937  xact_state->first = trans;
938  pgstat_info->trans = trans;
939 }
940 
941 /*
942  * Add a new (sub)transaction record if needed.
943  */
944 static void
946 {
947  int nest_level = GetCurrentTransactionNestLevel();
948 
949  if (pgstat_info->trans == NULL ||
950  pgstat_info->trans->nest_level != nest_level)
951  add_tabstat_xact_level(pgstat_info, nest_level);
952 }
953 
954 /*
955  * Whenever a table is truncated/dropped, we save its i/u/d counters so that
956  * they can be cleared, and if the (sub)xact that executed the truncate/drop
957  * later aborts, the counters can be restored to the saved (pre-truncate/drop)
958  * values.
959  *
960  * Note that for truncate we do this on the first truncate in any particular
961  * subxact level only.
962  */
963 static void
965 {
966  if (!trans->truncdropped || is_drop)
967  {
968  trans->inserted_pre_truncdrop = trans->tuples_inserted;
969  trans->updated_pre_truncdrop = trans->tuples_updated;
970  trans->deleted_pre_truncdrop = trans->tuples_deleted;
971  trans->truncdropped = true;
972  }
973 }
974 
975 /*
976  * restore counters when a truncate aborts
977  */
978 static void
980 {
981  if (trans->truncdropped)
982  {
983  trans->tuples_inserted = trans->inserted_pre_truncdrop;
984  trans->tuples_updated = trans->updated_pre_truncdrop;
985  trans->tuples_deleted = trans->deleted_pre_truncdrop;
986  }
987 }
bool IsAutoVacuumWorkerProcess(void)
Definition: autovacuum.c:3396
TimestampTz GetCurrentTimestamp(void)
Definition: timestamp.c:1649
unsigned short uint16
Definition: c.h:494
unsigned int uint32
Definition: c.h:495
#define PG_USED_FOR_ASSERTS_ONLY
Definition: c.h:171
#define Max(x, y)
Definition: c.h:987
uint32 TransactionId
Definition: c.h:641
bool IsSharedRelation(Oid relationId)
Definition: catalog.c:245
int64 TimestampTz
Definition: timestamp.h:39
Oid MyDatabaseId
Definition: globals.c:89
if(TABLE==NULL||TABLE_index==NULL)
Definition: isn.c:77
Assert(fmt[strlen(fmt) - 1] !='\n')
MemoryContext TopTransactionContext
Definition: mcxt.c:146
void pfree(void *pointer)
Definition: mcxt.c:1456
void * MemoryContextAllocZero(MemoryContext context, Size size)
Definition: mcxt.c:1064
void * palloc(Size size)
Definition: mcxt.c:1226
const void size_t len
bool pgstat_track_counts
Definition: pgstat.c:187
void * pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, Oid objoid)
Definition: pgstat.c:815
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:601
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:163
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:603
bool pgstat_lock_entry(PgStat_EntryRef *entry_ref, bool nowait)
Definition: pgstat_shmem.c:575
PgStat_EntryRef * pgstat_get_entry_ref_locked(PgStat_Kind kind, Oid dboid, Oid objoid, bool nowait)
Definition: pgstat_shmem.c:612
void pgstat_create_transactional(PgStat_Kind kind, Oid dboid, Oid objoid)
Definition: pgstat_xact.c:358
void pgstat_drop_transactional(PgStat_Kind kind, Oid dboid, Oid objoid)
Definition: pgstat_xact.c:380
PgStat_SubXactStatus * pgstat_get_xact_stack_level(int nest_level)
Definition: pgstat_xact.c:237
#define InvalidOid
Definition: postgres_ext.h:36
unsigned int Oid
Definition: postgres_ext.h:31
#define RelationGetRelid(relation)
Definition: rel.h:504
PgStat_StatTabEntry stats
PgStatShared_Common * shared_stats
PgStatShared_HashEntry * shared_entry
PgStat_Counter tuples_updated
Definition: pgstat.h:329
PgStat_Counter tuples_inserted
Definition: pgstat.h:328
PgStat_Counter tuples_returned
Definition: pgstat.h:326
PgStat_Counter blocks_hit
Definition: pgstat.h:325
PgStat_Counter blocks_fetched
Definition: pgstat.h:324
PgStat_Counter tuples_deleted
Definition: pgstat.h:330
PgStat_Counter tuples_fetched
Definition: pgstat.h:327
PgStat_Counter vacuum_count
Definition: pgstat.h:419
PgStat_Counter tuples_fetched
Definition: pgstat.h:402
PgStat_Counter ins_since_vacuum
Definition: pgstat.h:413
PgStat_Counter blocks_hit
Definition: pgstat.h:416
PgStat_Counter mod_since_analyze
Definition: pgstat.h:412
TimestampTz last_autovacuum_time
Definition: pgstat.h:420
PgStat_Counter analyze_count
Definition: pgstat.h:423
PgStat_Counter tuples_deleted
Definition: pgstat.h:406
PgStat_Counter tuples_hot_updated
Definition: pgstat.h:407
PgStat_Counter tuples_updated
Definition: pgstat.h:405
PgStat_Counter live_tuples
Definition: pgstat.h:410
PgStat_Counter numscans
Definition: pgstat.h:398
PgStat_Counter autovacuum_count
Definition: pgstat.h:421
TimestampTz last_analyze_time
Definition: pgstat.h:422
PgStat_Counter dead_tuples
Definition: pgstat.h:411
PgStat_Counter autoanalyze_count
Definition: pgstat.h:425
PgStat_Counter blocks_fetched
Definition: pgstat.h:415
PgStat_Counter tuples_returned
Definition: pgstat.h:401
TimestampTz last_autoanalyze_time
Definition: pgstat.h:424
TimestampTz lastscan
Definition: pgstat.h:399
PgStat_Counter tuples_inserted
Definition: pgstat.h:404
PgStat_Counter tuples_newpage_updated
Definition: pgstat.h:408
TimestampTz last_vacuum_time
Definition: pgstat.h:418
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:252
Form_pg_class rd_rel
Definition: rel.h:111
struct PgStat_TableStatus * pgstat_info
Definition: rel.h:254
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:1296
#define TWOPHASE_RM_PGSTAT_ID
Definition: twophase_rmgr.h:26
int GetCurrentTransactionNestLevel(void)
Definition: xact.c:914
TimestampTz GetCurrentTransactionStopTimestamp(void)
Definition: xact.c:876
static zic_t trans[TZ_MAX_LEAPS]
Definition: zic.c:402