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