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