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 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-2025, 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
1249 andres@anarazel.de 57 :CBC 250 : pgstat_copy_relation_stats(Relation dst, Relation src)
58 : : {
59 : : PgStat_StatTabEntry *srcstats;
60 : : PgStatShared_Relation *dstshstats;
61 : : PgStat_EntryRef *dst_ref;
62 : :
63 : 250 : srcstats = pgstat_fetch_stat_tabentry_ext(src->rd_rel->relisshared,
64 : : RelationGetRelid(src));
65 [ + + ]: 250 : if (!srcstats)
66 : 143 : return;
67 : :
68 : 107 : dst_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION,
69 : 107 : dst->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
70 [ - + ]: 107 : RelationGetRelid(dst),
71 : : false);
72 : :
73 : 107 : dstshstats = (PgStatShared_Relation *) dst_ref->shared_stats;
74 : 107 : dstshstats->stats = *srcstats;
75 : :
76 : 107 : 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 : 19158938 : pgstat_init_relation(Relation rel)
92 : : {
1265 93 : 19158938 : char relkind = rel->rd_rel->relkind;
94 : :
95 : : /*
96 : : * We only count stats for relations with storage and partitioned tables
97 : : */
98 [ + + + + : 19158938 : if (!RELKIND_HAS_STORAGE(relkind) && relkind != RELKIND_PARTITIONED_TABLE)
+ + + + +
+ + + ]
99 : : {
1249 100 : 74785 : rel->pgstat_enabled = false;
1265 101 : 74785 : rel->pgstat_info = NULL;
102 : 74785 : return;
103 : : }
104 : :
1249 105 [ + + ]: 19084153 : 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;
1265 112 : 197 : rel->pgstat_info = NULL;
113 : 197 : return;
114 : : }
115 : :
1249 116 : 19083956 : 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 : 847766 : pgstat_assoc_relation(Relation rel)
132 : : {
133 [ - + ]: 847766 : Assert(rel->pgstat_enabled);
134 [ - + ]: 847766 : Assert(rel->pgstat_info == NULL);
135 : :
136 : : /* Else find or make the PgStat_TableStatus entry, and update link */
137 : 1695532 : rel->pgstat_info = pgstat_prep_relation_pending(RelationGetRelid(rel),
138 : 847766 : rel->rd_rel->relisshared);
139 : :
140 : : /* don't allow link a stats to multiple relcache entries */
141 [ - + ]: 847766 : Assert(rel->pgstat_info->relation == NULL);
142 : :
143 : : /* mark this relation as the owner */
144 : 847766 : rel->pgstat_info->relation = rel;
145 : 847766 : }
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 : 1319909 : pgstat_unlink_relation(Relation rel)
153 : : {
154 : : /* remove the link to stats info if any */
155 [ + + ]: 1319909 : if (rel->pgstat_info == NULL)
156 : 472143 : return;
157 : :
158 : : /* link sanity check */
159 [ - + ]: 847766 : Assert(rel->pgstat_info->relation == rel);
160 : 847766 : rel->pgstat_info->relation = NULL;
161 : 847766 : rel->pgstat_info = NULL;
162 : : }
163 : :
164 : : /*
165 : : * Ensure that stats are dropped if transaction aborts.
166 : : */
167 : : void
168 : 65813 : pgstat_create_relation(Relation rel)
169 : : {
170 : 65813 : pgstat_create_transactional(PGSTAT_KIND_RELATION,
171 : 65813 : rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
172 [ + + ]: 65813 : RelationGetRelid(rel));
173 : 65813 : }
174 : :
175 : : /*
176 : : * Ensure that stats are dropped if transaction commits.
177 : : */
178 : : void
179 : 34031 : pgstat_drop_relation(Relation rel)
180 : : {
181 : 34031 : int nest_level = GetCurrentTransactionNestLevel();
182 : : PgStat_TableStatus *pgstat_info;
183 : :
184 : 34031 : pgstat_drop_transactional(PGSTAT_KIND_RELATION,
185 : 34031 : rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
186 [ - + ]: 34031 : RelationGetRelid(rel));
187 : :
188 [ + + + + : 34031 : if (!pgstat_should_count_relation(rel))
+ + ]
189 : 2239 : 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 : 31792 : pgstat_info = rel->pgstat_info;
196 [ + + ]: 31792 : if (pgstat_info->trans &&
197 [ + + ]: 627 : pgstat_info->trans->nest_level == nest_level)
198 : : {
199 : 624 : save_truncdrop_counters(pgstat_info->trans, true);
200 : 624 : pgstat_info->trans->tuples_inserted = 0;
201 : 624 : pgstat_info->trans->tuples_updated = 0;
202 : 624 : 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
1265 210 : 13120 : pgstat_report_vacuum(Oid tableoid, bool shared,
211 : : PgStat_Counter livetuples, PgStat_Counter deadtuples,
212 : : TimestampTz starttime)
213 : : {
214 : : PgStat_EntryRef *entry_ref;
215 : : PgStatShared_Relation *shtabentry;
216 : : PgStat_StatTabEntry *tabentry;
1249 217 [ + + ]: 13120 : Oid dboid = (shared ? InvalidOid : MyDatabaseId);
218 : : TimestampTz ts;
219 : : PgStat_Counter elapsedtime;
220 : :
221 [ - + ]: 13120 : if (!pgstat_track_counts)
1265 andres@anarazel.de 222 :UBC 0 : return;
223 : :
224 : : /* Store the data in the table's hash table entry. */
1249 andres@anarazel.de 225 :CBC 13120 : ts = GetCurrentTimestamp();
221 michael@paquier.xyz 226 : 13120 : elapsedtime = TimestampDifferenceMilliseconds(starttime, ts);
227 : :
228 : : /* block acquiring lock for the same reason as pgstat_report_autovac() */
1249 andres@anarazel.de 229 : 13120 : entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION,
230 : : dboid, tableoid, false);
231 : :
232 : 13120 : shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats;
233 : 13120 : tabentry = &shtabentry->stats;
234 : :
1005 michael@paquier.xyz 235 : 13120 : tabentry->live_tuples = livetuples;
236 : 13120 : 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 : 13120 : tabentry->ins_since_vacuum = 0;
249 : :
551 heikki.linnakangas@i 250 [ + + ]: 13120 : if (AmAutoVacuumWorkerProcess())
251 : : {
1005 michael@paquier.xyz 252 : 167 : tabentry->last_autovacuum_time = ts;
253 : 167 : tabentry->autovacuum_count++;
221 254 : 167 : tabentry->total_autovacuum_time += elapsedtime;
255 : : }
256 : : else
257 : : {
1005 258 : 12953 : tabentry->last_vacuum_time = ts;
1249 andres@anarazel.de 259 : 12953 : tabentry->vacuum_count++;
221 michael@paquier.xyz 260 : 12953 : tabentry->total_vacuum_time += elapsedtime;
261 : : }
262 : :
1249 andres@anarazel.de 263 : 13120 : 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 : : */
941 271 : 13120 : pgstat_flush_io(false);
228 michael@paquier.xyz 272 : 13120 : (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
1265 andres@anarazel.de 282 : 7982 : 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;
1249 289 [ + + ]: 7982 : Oid dboid = (rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId);
290 : : TimestampTz ts;
291 : : PgStat_Counter elapsedtime;
292 : :
293 [ - + ]: 7982 : if (!pgstat_track_counts)
1265 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 : : */
1249 andres@anarazel.de 308 [ + + + + :CBC 7982 : if (pgstat_should_count_relation(rel) &&
+ + ]
1265 309 [ + + ]: 7950 : rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
310 : : {
311 : : PgStat_TableXactStatus *trans;
312 : :
313 [ + + ]: 7671 : for (trans = rel->pgstat_info->trans; trans; trans = trans->upper)
314 : : {
315 : 79 : livetuples -= trans->tuples_inserted - trans->tuples_deleted;
316 : 79 : deadtuples -= trans->tuples_updated + trans->tuples_deleted;
317 : : }
318 : : /* count stuff inserted by already-aborted subxacts, too */
897 michael@paquier.xyz 319 : 7592 : deadtuples -= rel->pgstat_info->counts.delta_dead_tuples;
320 : : /* Since ANALYZE's counts are estimates, we could have underflowed */
1265 andres@anarazel.de 321 : 7592 : livetuples = Max(livetuples, 0);
322 : 7592 : deadtuples = Max(deadtuples, 0);
323 : : }
324 : :
325 : : /* Store the data in the table's hash table entry. */
221 michael@paquier.xyz 326 : 7982 : ts = GetCurrentTimestamp();
327 : 7982 : elapsedtime = TimestampDifferenceMilliseconds(starttime, ts);
328 : :
329 : : /* block acquiring lock for the same reason as pgstat_report_autovac() */
1249 andres@anarazel.de 330 : 7982 : entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, dboid,
331 : 7982 : RelationGetRelid(rel),
332 : : false);
333 : : /* can't get dropped while accessed */
334 [ + - - + ]: 7982 : Assert(entry_ref != NULL && entry_ref->shared_stats != NULL);
335 : :
336 : 7982 : shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats;
337 : 7982 : tabentry = &shtabentry->stats;
338 : :
1005 michael@paquier.xyz 339 : 7982 : tabentry->live_tuples = livetuples;
340 : 7982 : 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 : : */
1249 andres@anarazel.de 347 [ + + ]: 7982 : if (resetcounter)
1005 michael@paquier.xyz 348 : 7957 : tabentry->mod_since_analyze = 0;
349 : :
551 heikki.linnakangas@i 350 [ + + ]: 7982 : if (AmAutoVacuumWorkerProcess())
351 : : {
221 michael@paquier.xyz 352 : 283 : tabentry->last_autoanalyze_time = ts;
1005 353 : 283 : tabentry->autoanalyze_count++;
221 354 : 283 : tabentry->total_autoanalyze_time += elapsedtime;
355 : : }
356 : : else
357 : : {
358 : 7699 : tabentry->last_analyze_time = ts;
1249 andres@anarazel.de 359 : 7699 : tabentry->analyze_count++;
221 michael@paquier.xyz 360 : 7699 : tabentry->total_analyze_time += elapsedtime;
361 : : }
362 : :
1249 andres@anarazel.de 363 : 7982 : pgstat_unlock_entry(entry_ref);
364 : :
365 : : /* see pgstat_report_vacuum() */
941 366 : 7982 : pgstat_flush_io(false);
228 michael@paquier.xyz 367 : 7982 : (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO);
368 : : }
369 : :
370 : : /*
371 : : * count a tuple insertion of n tuples
372 : : */
373 : : void
1265 andres@anarazel.de 374 : 8741696 : pgstat_count_heap_insert(Relation rel, PgStat_Counter n)
375 : : {
1249 376 [ + + + + : 8741696 : if (pgstat_should_count_relation(rel))
+ + ]
377 : : {
1265 378 : 8554294 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
379 : :
380 : 8554294 : ensure_tabstat_xact_level(pgstat_info);
381 : 8554294 : pgstat_info->trans->tuples_inserted += n;
382 : : }
383 : 8741696 : }
384 : :
385 : : /*
386 : : * count a tuple update
387 : : */
388 : : void
898 pg@bowt.ie 389 : 302864 : pgstat_count_heap_update(Relation rel, bool hot, bool newpage)
390 : : {
391 [ + + - + ]: 302864 : Assert(!(hot && newpage));
392 : :
1249 andres@anarazel.de 393 [ + + - + : 302864 : if (pgstat_should_count_relation(rel))
+ + ]
394 : : {
1265 395 : 302862 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
396 : :
397 : 302862 : ensure_tabstat_xact_level(pgstat_info);
398 : 302862 : 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 [ + + ]: 302862 : if (hot)
897 michael@paquier.xyz 405 : 142130 : pgstat_info->counts.tuples_hot_updated++;
898 pg@bowt.ie 406 [ + + ]: 160732 : else if (newpage)
897 michael@paquier.xyz 407 : 148780 : pgstat_info->counts.tuples_newpage_updated++;
408 : : }
1265 andres@anarazel.de 409 : 302864 : }
410 : :
411 : : /*
412 : : * count a tuple deletion
413 : : */
414 : : void
415 : 1418489 : pgstat_count_heap_delete(Relation rel)
416 : : {
1249 417 [ - + - - : 1418489 : if (pgstat_should_count_relation(rel))
+ - ]
418 : : {
1265 419 : 1418489 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
420 : :
421 : 1418489 : ensure_tabstat_xact_level(pgstat_info);
422 : 1418489 : pgstat_info->trans->tuples_deleted++;
423 : : }
424 : 1418489 : }
425 : :
426 : : /*
427 : : * update tuple counters due to truncate
428 : : */
429 : : void
430 : 1714 : pgstat_count_truncate(Relation rel)
431 : : {
1249 432 [ + + + - : 1714 : if (pgstat_should_count_relation(rel))
+ - ]
433 : : {
1265 434 : 1714 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
435 : :
436 : 1714 : ensure_tabstat_xact_level(pgstat_info);
1249 437 : 1714 : save_truncdrop_counters(pgstat_info->trans, false);
1265 438 : 1714 : pgstat_info->trans->tuples_inserted = 0;
439 : 1714 : pgstat_info->trans->tuples_updated = 0;
440 : 1714 : pgstat_info->trans->tuples_deleted = 0;
441 : : }
442 : 1714 : }
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 : 18896 : pgstat_update_heap_dead_tuples(Relation rel, int delta)
454 : : {
1249 455 [ - + - - : 18896 : if (pgstat_should_count_relation(rel))
+ - ]
456 : : {
1265 457 : 18896 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
458 : :
897 michael@paquier.xyz 459 : 18896 : pgstat_info->counts.delta_dead_tuples -= delta;
460 : : }
1265 andres@anarazel.de 461 : 18896 : }
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 *
1249 470 : 4565 : pgstat_fetch_stat_tabentry(Oid relid)
471 : : {
1021 472 : 4565 : return pgstat_fetch_stat_tabentry_ext(IsSharedRelation(relid), relid);
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.
478 : : */
479 : : PgStat_StatTabEntry *
1249 480 : 10811 : pgstat_fetch_stat_tabentry_ext(bool shared, Oid reloid)
481 : : {
482 [ + + ]: 10811 : Oid dboid = (shared ? InvalidOid : MyDatabaseId);
483 : :
484 : 10811 : return (PgStat_StatTabEntry *)
485 : 10811 : pgstat_fetch_entry(PGSTAT_KIND_RELATION, dboid, reloid);
486 : : }
487 : :
488 : : /*
489 : : * find any existing PgStat_TableStatus entry for rel
490 : : *
491 : : * Find any existing PgStat_TableStatus entry for rel_id in the current
492 : : * database. If not found, try finding from shared tables.
493 : : *
494 : : * If an entry is found, copy it and increment the copy's counters with their
495 : : * subtransaction counterparts, then return the copy. The caller may need to
496 : : * pfree() the copy.
497 : : *
498 : : * If no entry found, return NULL, don't create a new one.
499 : : */
500 : : PgStat_TableStatus *
1265 501 : 24 : find_tabstat_entry(Oid rel_id)
502 : : {
503 : : PgStat_EntryRef *entry_ref;
504 : : PgStat_TableXactStatus *trans;
677 michael@paquier.xyz 505 : 24 : PgStat_TableStatus *tabentry = NULL;
506 : 24 : PgStat_TableStatus *tablestatus = NULL;
507 : :
1249 andres@anarazel.de 508 : 24 : entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_RELATION, MyDatabaseId, rel_id);
509 [ + + ]: 24 : if (!entry_ref)
510 : : {
511 : 6 : entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_RELATION, InvalidOid, rel_id);
677 michael@paquier.xyz 512 [ + - ]: 6 : if (!entry_ref)
513 : 6 : return tablestatus;
514 : : }
515 : :
516 : 18 : tabentry = (PgStat_TableStatus *) entry_ref->pending;
517 : 18 : tablestatus = palloc(sizeof(PgStat_TableStatus));
518 : 18 : *tablestatus = *tabentry;
519 : :
520 : : /*
521 : : * Reset tablestatus->trans in the copy of PgStat_TableStatus as it may
522 : : * point to a shared memory area. Its data is saved below, so removing it
523 : : * does not matter.
524 : : */
525 : 18 : tablestatus->trans = NULL;
526 : :
527 : : /*
528 : : * Live subtransaction counts are not included yet. This is not a hot
529 : : * code path so reconcile tuples_inserted, tuples_updated and
530 : : * tuples_deleted even if the caller may not be interested in this data.
531 : : */
532 [ + + ]: 42 : for (trans = tabentry->trans; trans != NULL; trans = trans->upper)
533 : : {
534 : 24 : tablestatus->counts.tuples_inserted += trans->tuples_inserted;
535 : 24 : tablestatus->counts.tuples_updated += trans->tuples_updated;
536 : 24 : tablestatus->counts.tuples_deleted += trans->tuples_deleted;
537 : : }
538 : :
539 : 18 : return tablestatus;
540 : : }
541 : :
542 : : /*
543 : : * Perform relation stats specific end-of-transaction work. Helper for
544 : : * AtEOXact_PgStat.
545 : : *
546 : : * Transfer transactional insert/update counts into the base tabstat entries.
547 : : * We don't bother to free any of the transactional state, since it's all in
548 : : * TopTransactionContext and will go away anyway.
549 : : */
550 : : void
1265 andres@anarazel.de 551 : 124608 : AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit)
552 : : {
553 : : PgStat_TableXactStatus *trans;
554 : :
555 [ + + ]: 464732 : for (trans = xact_state->first; trans != NULL; trans = trans->next)
556 : : {
557 : : PgStat_TableStatus *tabstat;
558 : :
559 [ - + ]: 340124 : Assert(trans->nest_level == 1);
560 [ - + ]: 340124 : Assert(trans->upper == NULL);
561 : 340124 : tabstat = trans->parent;
562 [ - + ]: 340124 : Assert(tabstat->trans == trans);
563 : : /* restore pre-truncate/drop stats (if any) in case of aborted xact */
564 [ + + ]: 340124 : if (!isCommit)
1249 565 : 11317 : restore_truncdrop_counters(trans);
566 : : /* count attempted actions regardless of commit/abort */
897 michael@paquier.xyz 567 : 340124 : tabstat->counts.tuples_inserted += trans->tuples_inserted;
568 : 340124 : tabstat->counts.tuples_updated += trans->tuples_updated;
569 : 340124 : tabstat->counts.tuples_deleted += trans->tuples_deleted;
1265 andres@anarazel.de 570 [ + + ]: 340124 : if (isCommit)
571 : : {
897 michael@paquier.xyz 572 : 328807 : tabstat->counts.truncdropped = trans->truncdropped;
1265 andres@anarazel.de 573 [ + + ]: 328807 : if (trans->truncdropped)
574 : : {
575 : : /* forget live/dead stats seen by backend thus far */
897 michael@paquier.xyz 576 : 2151 : tabstat->counts.delta_live_tuples = 0;
577 : 2151 : tabstat->counts.delta_dead_tuples = 0;
578 : : }
579 : : /* insert adds a live tuple, delete removes one */
580 : 328807 : tabstat->counts.delta_live_tuples +=
1265 andres@anarazel.de 581 : 328807 : trans->tuples_inserted - trans->tuples_deleted;
582 : : /* update and delete each create a dead tuple */
897 michael@paquier.xyz 583 : 328807 : tabstat->counts.delta_dead_tuples +=
1265 andres@anarazel.de 584 : 328807 : trans->tuples_updated + trans->tuples_deleted;
585 : : /* insert, update, delete each count as one change event */
897 michael@paquier.xyz 586 : 328807 : tabstat->counts.changed_tuples +=
1265 andres@anarazel.de 587 : 328807 : trans->tuples_inserted + trans->tuples_updated +
588 : 328807 : trans->tuples_deleted;
589 : : }
590 : : else
591 : : {
592 : : /* inserted tuples are dead, deleted tuples are unaffected */
897 michael@paquier.xyz 593 : 11317 : tabstat->counts.delta_dead_tuples +=
1265 andres@anarazel.de 594 : 11317 : trans->tuples_inserted + trans->tuples_updated;
595 : : /* an aborted xact generates no changed_tuple events */
596 : : }
597 : 340124 : tabstat->trans = NULL;
598 : : }
599 : 124608 : }
600 : :
601 : : /*
602 : : * Perform relation stats specific end-of-sub-transaction work. Helper for
603 : : * AtEOSubXact_PgStat.
604 : : *
605 : : * Transfer transactional insert/update counts into the next higher
606 : : * subtransaction state.
607 : : */
608 : : void
609 : 3293 : AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth)
610 : : {
611 : : PgStat_TableXactStatus *trans;
612 : : PgStat_TableXactStatus *next_trans;
613 : :
614 [ + + ]: 6895 : for (trans = xact_state->first; trans != NULL; trans = next_trans)
615 : : {
616 : : PgStat_TableStatus *tabstat;
617 : :
618 : 3602 : next_trans = trans->next;
619 [ - + ]: 3602 : Assert(trans->nest_level == nestDepth);
620 : 3602 : tabstat = trans->parent;
621 [ - + ]: 3602 : Assert(tabstat->trans == trans);
622 : :
623 [ + + ]: 3602 : if (isCommit)
624 : : {
625 [ + + + + ]: 2842 : if (trans->upper && trans->upper->nest_level == nestDepth - 1)
626 : : {
627 [ + + ]: 1726 : if (trans->truncdropped)
628 : : {
629 : : /* propagate the truncate/drop status one level up */
1249 630 : 12 : save_truncdrop_counters(trans->upper, false);
631 : : /* replace upper xact stats with ours */
1265 632 : 12 : trans->upper->tuples_inserted = trans->tuples_inserted;
633 : 12 : trans->upper->tuples_updated = trans->tuples_updated;
634 : 12 : trans->upper->tuples_deleted = trans->tuples_deleted;
635 : : }
636 : : else
637 : : {
638 : 1714 : trans->upper->tuples_inserted += trans->tuples_inserted;
639 : 1714 : trans->upper->tuples_updated += trans->tuples_updated;
640 : 1714 : trans->upper->tuples_deleted += trans->tuples_deleted;
641 : : }
642 : 1726 : tabstat->trans = trans->upper;
643 : 1726 : pfree(trans);
644 : : }
645 : : else
646 : : {
647 : : /*
648 : : * When there isn't an immediate parent state, we can just
649 : : * reuse the record instead of going through a palloc/pfree
650 : : * pushup (this works since it's all in TopTransactionContext
651 : : * anyway). We have to re-link it into the parent level,
652 : : * though, and that might mean pushing a new entry into the
653 : : * pgStatXactStack.
654 : : */
655 : : PgStat_SubXactStatus *upper_xact_state;
656 : :
1249 657 : 1116 : upper_xact_state = pgstat_get_xact_stack_level(nestDepth - 1);
1265 658 : 1116 : trans->next = upper_xact_state->first;
659 : 1116 : upper_xact_state->first = trans;
660 : 1116 : trans->nest_level = nestDepth - 1;
661 : : }
662 : : }
663 : : else
664 : : {
665 : : /*
666 : : * On abort, update top-level tabstat counts, then forget the
667 : : * subtransaction
668 : : */
669 : :
670 : : /* first restore values obliterated by truncate/drop */
1249 671 : 760 : restore_truncdrop_counters(trans);
672 : : /* count attempted actions regardless of commit/abort */
897 michael@paquier.xyz 673 : 760 : tabstat->counts.tuples_inserted += trans->tuples_inserted;
674 : 760 : tabstat->counts.tuples_updated += trans->tuples_updated;
675 : 760 : tabstat->counts.tuples_deleted += trans->tuples_deleted;
676 : : /* inserted tuples are dead, deleted tuples are unaffected */
677 : 760 : tabstat->counts.delta_dead_tuples +=
1265 andres@anarazel.de 678 : 760 : trans->tuples_inserted + trans->tuples_updated;
679 : 760 : tabstat->trans = trans->upper;
680 : 760 : pfree(trans);
681 : : }
682 : : }
683 : 3293 : }
684 : :
685 : : /*
686 : : * Generate 2PC records for all the pending transaction-dependent relation
687 : : * stats.
688 : : */
689 : : void
690 : 281 : AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
691 : : {
692 : : PgStat_TableXactStatus *trans;
693 : :
694 [ + + ]: 636 : for (trans = xact_state->first; trans != NULL; trans = trans->next)
695 : : {
696 : : PgStat_TableStatus *tabstat PG_USED_FOR_ASSERTS_ONLY;
697 : : TwoPhasePgStatRecord record;
698 : :
699 [ - + ]: 355 : Assert(trans->nest_level == 1);
700 [ - + ]: 355 : Assert(trans->upper == NULL);
701 : 355 : tabstat = trans->parent;
702 [ - + ]: 355 : Assert(tabstat->trans == trans);
703 : :
704 : 355 : record.tuples_inserted = trans->tuples_inserted;
705 : 355 : record.tuples_updated = trans->tuples_updated;
706 : 355 : record.tuples_deleted = trans->tuples_deleted;
707 : 355 : record.inserted_pre_truncdrop = trans->inserted_pre_truncdrop;
708 : 355 : record.updated_pre_truncdrop = trans->updated_pre_truncdrop;
709 : 355 : record.deleted_pre_truncdrop = trans->deleted_pre_truncdrop;
897 michael@paquier.xyz 710 : 355 : record.id = tabstat->id;
711 : 355 : record.shared = tabstat->shared;
712 : 355 : record.truncdropped = trans->truncdropped;
713 : :
1265 andres@anarazel.de 714 : 355 : RegisterTwoPhaseRecord(TWOPHASE_RM_PGSTAT_ID, 0,
715 : : &record, sizeof(TwoPhasePgStatRecord));
716 : : }
717 : 281 : }
718 : :
719 : : /*
720 : : * All we need do here is unlink the transaction stats state from the
721 : : * nontransactional state. The nontransactional action counts will be
722 : : * reported to the stats system immediately, while the effects on live and
723 : : * dead tuple counts are preserved in the 2PC state file.
724 : : *
725 : : * Note: AtEOXact_PgStat_Relations is not called during PREPARE.
726 : : */
727 : : void
728 : 281 : PostPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
729 : : {
730 : : PgStat_TableXactStatus *trans;
731 : :
732 [ + + ]: 636 : for (trans = xact_state->first; trans != NULL; trans = trans->next)
733 : : {
734 : : PgStat_TableStatus *tabstat;
735 : :
736 : 355 : tabstat = trans->parent;
737 : 355 : tabstat->trans = NULL;
738 : : }
739 : 281 : }
740 : :
741 : : /*
742 : : * 2PC processing routine for COMMIT PREPARED case.
743 : : *
744 : : * Load the saved counts into our local pgstats state.
745 : : */
746 : : void
61 michael@paquier.xyz 747 :GNC 304 : pgstat_twophase_postcommit(FullTransactionId fxid, uint16 info,
748 : : void *recdata, uint32 len)
749 : : {
1265 andres@anarazel.de 750 :CBC 304 : TwoPhasePgStatRecord *rec = (TwoPhasePgStatRecord *) recdata;
751 : : PgStat_TableStatus *pgstat_info;
752 : :
753 : : /* Find or create a tabstat entry for the rel */
897 michael@paquier.xyz 754 : 304 : pgstat_info = pgstat_prep_relation_pending(rec->id, rec->shared);
755 : :
756 : : /* Same math as in AtEOXact_PgStat, commit case */
757 : 304 : pgstat_info->counts.tuples_inserted += rec->tuples_inserted;
758 : 304 : pgstat_info->counts.tuples_updated += rec->tuples_updated;
759 : 304 : pgstat_info->counts.tuples_deleted += rec->tuples_deleted;
760 : 304 : pgstat_info->counts.truncdropped = rec->truncdropped;
761 [ + + ]: 304 : if (rec->truncdropped)
762 : : {
763 : : /* forget live/dead stats seen by backend thus far */
764 : 2 : pgstat_info->counts.delta_live_tuples = 0;
765 : 2 : pgstat_info->counts.delta_dead_tuples = 0;
766 : : }
767 : 304 : pgstat_info->counts.delta_live_tuples +=
1265 andres@anarazel.de 768 : 304 : rec->tuples_inserted - rec->tuples_deleted;
897 michael@paquier.xyz 769 : 304 : pgstat_info->counts.delta_dead_tuples +=
1265 andres@anarazel.de 770 : 304 : rec->tuples_updated + rec->tuples_deleted;
897 michael@paquier.xyz 771 : 304 : pgstat_info->counts.changed_tuples +=
1265 andres@anarazel.de 772 : 304 : rec->tuples_inserted + rec->tuples_updated +
773 : 304 : rec->tuples_deleted;
774 : 304 : }
775 : :
776 : : /*
777 : : * 2PC processing routine for ROLLBACK PREPARED case.
778 : : *
779 : : * Load the saved counts into our local pgstats state, but treat them
780 : : * as aborted.
781 : : */
782 : : void
61 michael@paquier.xyz 783 :GNC 63 : pgstat_twophase_postabort(FullTransactionId fxid, uint16 info,
784 : : void *recdata, uint32 len)
785 : : {
1265 andres@anarazel.de 786 :CBC 63 : TwoPhasePgStatRecord *rec = (TwoPhasePgStatRecord *) recdata;
787 : : PgStat_TableStatus *pgstat_info;
788 : :
789 : : /* Find or create a tabstat entry for the rel */
897 michael@paquier.xyz 790 : 63 : pgstat_info = pgstat_prep_relation_pending(rec->id, rec->shared);
791 : :
792 : : /* Same math as in AtEOXact_PgStat, abort case */
793 [ + + ]: 63 : if (rec->truncdropped)
794 : : {
1265 andres@anarazel.de 795 : 4 : rec->tuples_inserted = rec->inserted_pre_truncdrop;
796 : 4 : rec->tuples_updated = rec->updated_pre_truncdrop;
797 : 4 : rec->tuples_deleted = rec->deleted_pre_truncdrop;
798 : : }
897 michael@paquier.xyz 799 : 63 : pgstat_info->counts.tuples_inserted += rec->tuples_inserted;
800 : 63 : pgstat_info->counts.tuples_updated += rec->tuples_updated;
801 : 63 : pgstat_info->counts.tuples_deleted += rec->tuples_deleted;
802 : 63 : pgstat_info->counts.delta_dead_tuples +=
1265 andres@anarazel.de 803 : 63 : rec->tuples_inserted + rec->tuples_updated;
804 : 63 : }
805 : :
806 : : /*
807 : : * Flush out pending stats for the entry
808 : : *
809 : : * If nowait is true and the lock could not be immediately acquired, returns
810 : : * false without flushing the entry. Otherwise returns true.
811 : : *
812 : : * Some of the stats are copied to the corresponding pending database stats
813 : : * entry when successfully flushing.
814 : : */
815 : : bool
1249 816 : 774128 : pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
817 : : {
818 : : Oid dboid;
819 : : PgStat_TableStatus *lstats; /* pending stats entry */
820 : : PgStatShared_Relation *shtabstats;
821 : : PgStat_StatTabEntry *tabentry; /* table entry of shared stats */
822 : : PgStat_StatDBEntry *dbentry; /* pending database entry */
823 : :
824 : 774128 : dboid = entry_ref->shared_entry->key.dboid;
825 : 774128 : lstats = (PgStat_TableStatus *) entry_ref->pending;
826 : 774128 : shtabstats = (PgStatShared_Relation *) entry_ref->shared_stats;
827 : :
828 : : /*
829 : : * Ignore entries that didn't accumulate any actual counts, such as
830 : : * indexes that were opened by the planner but not used.
831 : : */
309 michael@paquier.xyz 832 [ + + ]: 774128 : if (pg_memory_is_all_zeros(&lstats->counts,
833 : : sizeof(struct PgStat_TableCounts)))
1249 andres@anarazel.de 834 : 2661 : return true;
835 : :
836 [ + + ]: 771467 : if (!pgstat_lock_entry(entry_ref, nowait))
837 : 2 : return false;
838 : :
839 : : /* add the values to the shared entry. */
840 : 771465 : tabentry = &shtabstats->stats;
841 : :
897 michael@paquier.xyz 842 : 771465 : tabentry->numscans += lstats->counts.numscans;
843 [ + + ]: 771465 : if (lstats->counts.numscans)
844 : : {
1058 andres@anarazel.de 845 : 473561 : TimestampTz t = GetCurrentTransactionStopTimestamp();
846 : :
847 [ + + ]: 473561 : if (t > tabentry->lastscan)
848 : 464730 : tabentry->lastscan = t;
849 : : }
897 michael@paquier.xyz 850 : 771465 : tabentry->tuples_returned += lstats->counts.tuples_returned;
851 : 771465 : tabentry->tuples_fetched += lstats->counts.tuples_fetched;
852 : 771465 : tabentry->tuples_inserted += lstats->counts.tuples_inserted;
853 : 771465 : tabentry->tuples_updated += lstats->counts.tuples_updated;
854 : 771465 : tabentry->tuples_deleted += lstats->counts.tuples_deleted;
855 : 771465 : tabentry->tuples_hot_updated += lstats->counts.tuples_hot_updated;
856 : 771465 : tabentry->tuples_newpage_updated += lstats->counts.tuples_newpage_updated;
857 : :
858 : : /*
859 : : * If table was truncated/dropped, first reset the live/dead counters.
860 : : */
861 [ + + ]: 771465 : if (lstats->counts.truncdropped)
862 : : {
1005 863 : 391 : tabentry->live_tuples = 0;
864 : 391 : tabentry->dead_tuples = 0;
865 : 391 : tabentry->ins_since_vacuum = 0;
866 : : }
867 : :
897 868 : 771465 : tabentry->live_tuples += lstats->counts.delta_live_tuples;
869 : 771465 : tabentry->dead_tuples += lstats->counts.delta_dead_tuples;
870 : 771465 : tabentry->mod_since_analyze += lstats->counts.changed_tuples;
871 : :
872 : : /*
873 : : * Using tuples_inserted to update ins_since_vacuum does mean that we'll
874 : : * track aborted inserts too. This isn't ideal, but otherwise probably
875 : : * not worth adding an extra field for. It may just amount to autovacuums
876 : : * triggering for inserts more often than they maybe should, which is
877 : : * probably not going to be common enough to be too concerned about here.
878 : : */
879 : 771465 : tabentry->ins_since_vacuum += lstats->counts.tuples_inserted;
880 : :
881 : 771465 : tabentry->blocks_fetched += lstats->counts.blocks_fetched;
882 : 771465 : tabentry->blocks_hit += lstats->counts.blocks_hit;
883 : :
884 : : /* Clamp live_tuples in case of negative delta_live_tuples */
1005 885 : 771465 : tabentry->live_tuples = Max(tabentry->live_tuples, 0);
886 : : /* Likewise for dead_tuples */
887 : 771465 : tabentry->dead_tuples = Max(tabentry->dead_tuples, 0);
888 : :
1249 andres@anarazel.de 889 : 771465 : pgstat_unlock_entry(entry_ref);
890 : :
891 : : /* The entry was successfully flushed, add the same to database stats */
892 : 771465 : dbentry = pgstat_prep_database_pending(dboid);
897 michael@paquier.xyz 893 : 771465 : dbentry->tuples_returned += lstats->counts.tuples_returned;
894 : 771465 : dbentry->tuples_fetched += lstats->counts.tuples_fetched;
895 : 771465 : dbentry->tuples_inserted += lstats->counts.tuples_inserted;
896 : 771465 : dbentry->tuples_updated += lstats->counts.tuples_updated;
897 : 771465 : dbentry->tuples_deleted += lstats->counts.tuples_deleted;
898 : 771465 : dbentry->blocks_fetched += lstats->counts.blocks_fetched;
899 : 771465 : dbentry->blocks_hit += lstats->counts.blocks_hit;
900 : :
1249 andres@anarazel.de 901 : 771465 : return true;
902 : : }
903 : :
904 : : void
905 : 806404 : pgstat_relation_delete_pending_cb(PgStat_EntryRef *entry_ref)
906 : : {
907 : 806404 : PgStat_TableStatus *pending = (PgStat_TableStatus *) entry_ref->pending;
908 : :
909 [ + + ]: 806404 : if (pending->relation)
910 : 747192 : pgstat_unlink_relation(pending->relation);
1265 911 : 806404 : }
912 : :
913 : : /*
914 : : * Find or create a PgStat_TableStatus entry for rel. New entry is created and
915 : : * initialized if not exists.
916 : : */
917 : : static PgStat_TableStatus *
1249 918 : 848133 : pgstat_prep_relation_pending(Oid rel_id, bool isshared)
919 : : {
920 : : PgStat_EntryRef *entry_ref;
921 : : PgStat_TableStatus *pending;
922 : :
923 [ + + ]: 848133 : entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_RELATION,
924 : : isshared ? InvalidOid : MyDatabaseId,
925 : : rel_id, NULL);
926 : 848133 : pending = entry_ref->pending;
897 michael@paquier.xyz 927 : 848133 : pending->id = rel_id;
928 : 848133 : pending->shared = isshared;
929 : :
1249 andres@anarazel.de 930 : 848133 : return pending;
931 : : }
932 : :
933 : : /*
934 : : * add a new (sub)transaction state record
935 : : */
936 : : static void
1265 937 : 342965 : add_tabstat_xact_level(PgStat_TableStatus *pgstat_info, int nest_level)
938 : : {
939 : : PgStat_SubXactStatus *xact_state;
940 : : PgStat_TableXactStatus *trans;
941 : :
942 : : /*
943 : : * If this is the first rel to be modified at the current nest level, we
944 : : * first have to push a transaction stack entry.
945 : : */
1249 946 : 342965 : xact_state = pgstat_get_xact_stack_level(nest_level);
947 : :
948 : : /* Now make a per-table stack entry */
949 : : trans = (PgStat_TableXactStatus *)
1265 950 : 342965 : MemoryContextAllocZero(TopTransactionContext,
951 : : sizeof(PgStat_TableXactStatus));
952 : 342965 : trans->nest_level = nest_level;
953 : 342965 : trans->upper = pgstat_info->trans;
954 : 342965 : trans->parent = pgstat_info;
955 : 342965 : trans->next = xact_state->first;
956 : 342965 : xact_state->first = trans;
957 : 342965 : pgstat_info->trans = trans;
958 : 342965 : }
959 : :
960 : : /*
961 : : * Add a new (sub)transaction record if needed.
962 : : */
963 : : static void
964 : 10277359 : ensure_tabstat_xact_level(PgStat_TableStatus *pgstat_info)
965 : : {
966 : 10277359 : int nest_level = GetCurrentTransactionNestLevel();
967 : :
968 [ + + ]: 10277359 : if (pgstat_info->trans == NULL ||
969 [ + + ]: 9936677 : pgstat_info->trans->nest_level != nest_level)
970 : 342965 : add_tabstat_xact_level(pgstat_info, nest_level);
971 : 10277359 : }
972 : :
973 : : /*
974 : : * Whenever a table is truncated/dropped, we save its i/u/d counters so that
975 : : * they can be cleared, and if the (sub)xact that executed the truncate/drop
976 : : * later aborts, the counters can be restored to the saved (pre-truncate/drop)
977 : : * values.
978 : : *
979 : : * Note that for truncate we do this on the first truncate in any particular
980 : : * subxact level only.
981 : : */
982 : : static void
1249 983 : 2350 : save_truncdrop_counters(PgStat_TableXactStatus *trans, bool is_drop)
984 : : {
1265 985 [ + + + + ]: 2350 : if (!trans->truncdropped || is_drop)
986 : : {
987 : 2321 : trans->inserted_pre_truncdrop = trans->tuples_inserted;
988 : 2321 : trans->updated_pre_truncdrop = trans->tuples_updated;
989 : 2321 : trans->deleted_pre_truncdrop = trans->tuples_deleted;
990 : 2321 : trans->truncdropped = true;
991 : : }
992 : 2350 : }
993 : :
994 : : /*
995 : : * restore counters when a truncate aborts
996 : : */
997 : : static void
1249 998 : 12077 : restore_truncdrop_counters(PgStat_TableXactStatus *trans)
999 : : {
1265 1000 [ + + ]: 12077 : if (trans->truncdropped)
1001 : : {
1002 : 144 : trans->tuples_inserted = trans->inserted_pre_truncdrop;
1003 : 144 : trans->tuples_updated = trans->updated_pre_truncdrop;
1004 : 144 : trans->tuples_deleted = trans->deleted_pre_truncdrop;
1005 : : }
1006 : 12077 : }
|