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