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