LCOV - code coverage report
Current view: top level - src/backend/utils/activity - pgstat_relation.c (source / functions) Hit Total Coverage
Test: PostgreSQL 18devel Lines: 339 342 99.1 %
Date: 2024-11-21 08:14:44 Functions: 29 29 100.0 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.14