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