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-2024, 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 "postmaster/autovacuum.h"
24 : #include "utils/memutils.h"
25 : #include "utils/pgstat_internal.h"
26 : #include "utils/rel.h"
27 : #include "utils/timestamp.h"
28 :
29 :
30 : /* Record that's written to 2PC state file when pgstat state is persisted */
31 : typedef struct TwoPhasePgStatRecord
32 : {
33 : PgStat_Counter tuples_inserted; /* tuples inserted in xact */
34 : PgStat_Counter tuples_updated; /* tuples updated in xact */
35 : PgStat_Counter tuples_deleted; /* tuples deleted in xact */
36 : /* tuples i/u/d prior to truncate/drop */
37 : PgStat_Counter inserted_pre_truncdrop;
38 : PgStat_Counter updated_pre_truncdrop;
39 : PgStat_Counter deleted_pre_truncdrop;
40 : Oid id; /* table's OID */
41 : bool shared; /* is it a shared catalog? */
42 : bool truncdropped; /* was the relation truncated/dropped? */
43 : } TwoPhasePgStatRecord;
44 :
45 :
46 : static PgStat_TableStatus *pgstat_prep_relation_pending(Oid rel_id, bool isshared);
47 : static void add_tabstat_xact_level(PgStat_TableStatus *pgstat_info, int nest_level);
48 : static void ensure_tabstat_xact_level(PgStat_TableStatus *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 : /*
54 : * Copy stats between relations. This is used for things like REINDEX
55 : * CONCURRENTLY.
56 : */
57 : void
58 492 : pgstat_copy_relation_stats(Relation dst, Relation src)
59 : {
60 : PgStat_StatTabEntry *srcstats;
61 : PgStatShared_Relation *dstshstats;
62 : PgStat_EntryRef *dst_ref;
63 :
64 492 : srcstats = pgstat_fetch_stat_tabentry_ext(src->rd_rel->relisshared,
65 : RelationGetRelid(src));
66 492 : if (!srcstats)
67 274 : return;
68 :
69 218 : dst_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION,
70 218 : dst->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
71 : RelationGetRelid(dst),
72 : false);
73 :
74 218 : dstshstats = (PgStatShared_Relation *) dst_ref->shared_stats;
75 218 : dstshstats->stats = *srcstats;
76 :
77 218 : pgstat_unlock_entry(dst_ref);
78 : }
79 :
80 : /*
81 : * Initialize a relcache entry to count access statistics. Called whenever a
82 : * relation is opened.
83 : *
84 : * We assume that a relcache entry's pgstat_info field is zeroed by relcache.c
85 : * when the relcache entry is made; thereafter it is long-lived data.
86 : *
87 : * This does not create a reference to a stats entry in shared memory, nor
88 : * allocate memory for the pending stats. That happens in
89 : * pgstat_assoc_relation().
90 : */
91 : void
92 31137090 : pgstat_init_relation(Relation rel)
93 : {
94 31137090 : char relkind = rel->rd_rel->relkind;
95 :
96 : /*
97 : * We only count stats for relations with storage and partitioned tables
98 : */
99 31137090 : if (!RELKIND_HAS_STORAGE(relkind) && relkind != RELKIND_PARTITIONED_TABLE)
100 : {
101 126722 : rel->pgstat_enabled = false;
102 126722 : rel->pgstat_info = NULL;
103 126722 : return;
104 : }
105 :
106 31010368 : if (!pgstat_track_counts)
107 : {
108 344 : if (rel->pgstat_info)
109 20 : pgstat_unlink_relation(rel);
110 :
111 : /* We're not counting at all */
112 344 : rel->pgstat_enabled = false;
113 344 : rel->pgstat_info = NULL;
114 344 : return;
115 : }
116 :
117 31010024 : rel->pgstat_enabled = true;
118 : }
119 :
120 : /*
121 : * Prepare for statistics for this relation to be collected.
122 : *
123 : * This ensures we have a reference to the stats entry before stats can be
124 : * generated. That is important because a relation drop in another connection
125 : * could otherwise lead to the stats entry being dropped, which then later
126 : * would get recreated when flushing stats.
127 : *
128 : * This is separate from pgstat_init_relation() as it is not uncommon for
129 : * relcache entries to be opened without ever getting stats reported.
130 : */
131 : void
132 1380274 : pgstat_assoc_relation(Relation rel)
133 : {
134 : Assert(rel->pgstat_enabled);
135 : Assert(rel->pgstat_info == NULL);
136 :
137 : /* Else find or make the PgStat_TableStatus entry, and update link */
138 2760548 : rel->pgstat_info = pgstat_prep_relation_pending(RelationGetRelid(rel),
139 1380274 : rel->rd_rel->relisshared);
140 :
141 : /* don't allow link a stats to multiple relcache entries */
142 : Assert(rel->pgstat_info->relation == NULL);
143 :
144 : /* mark this relation as the owner */
145 1380274 : rel->pgstat_info->relation = rel;
146 1380274 : }
147 :
148 : /*
149 : * Break the mutual link between a relcache entry and pending stats entry.
150 : * This must be called whenever one end of the link is removed.
151 : */
152 : void
153 2166146 : pgstat_unlink_relation(Relation rel)
154 : {
155 : /* remove the link to stats info if any */
156 2166146 : if (rel->pgstat_info == NULL)
157 785872 : return;
158 :
159 : /* link sanity check */
160 : Assert(rel->pgstat_info->relation == rel);
161 1380274 : rel->pgstat_info->relation = NULL;
162 1380274 : rel->pgstat_info = NULL;
163 : }
164 :
165 : /*
166 : * Ensure that stats are dropped if transaction aborts.
167 : */
168 : void
169 118630 : pgstat_create_relation(Relation rel)
170 : {
171 118630 : pgstat_create_transactional(PGSTAT_KIND_RELATION,
172 118630 : rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
173 : RelationGetRelid(rel));
174 118630 : }
175 :
176 : /*
177 : * Ensure that stats are dropped if transaction commits.
178 : */
179 : void
180 65218 : pgstat_drop_relation(Relation rel)
181 : {
182 65218 : int nest_level = GetCurrentTransactionNestLevel();
183 : PgStat_TableStatus *pgstat_info;
184 :
185 65218 : pgstat_drop_transactional(PGSTAT_KIND_RELATION,
186 65218 : rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
187 : RelationGetRelid(rel));
188 :
189 65218 : if (!pgstat_should_count_relation(rel))
190 4300 : return;
191 :
192 : /*
193 : * Transactionally set counters to 0. That ensures that accesses to
194 : * pg_stat_xact_all_tables inside the transaction show 0.
195 : */
196 60918 : pgstat_info = rel->pgstat_info;
197 60918 : if (pgstat_info->trans &&
198 872 : pgstat_info->trans->nest_level == nest_level)
199 : {
200 866 : save_truncdrop_counters(pgstat_info->trans, true);
201 866 : pgstat_info->trans->tuples_inserted = 0;
202 866 : pgstat_info->trans->tuples_updated = 0;
203 866 : pgstat_info->trans->tuples_deleted = 0;
204 : }
205 : }
206 :
207 : /*
208 : * Report that the table was just vacuumed and flush IO statistics.
209 : */
210 : void
211 19080 : pgstat_report_vacuum(Oid tableoid, bool shared,
212 : PgStat_Counter livetuples, PgStat_Counter deadtuples)
213 : {
214 : PgStat_EntryRef *entry_ref;
215 : PgStatShared_Relation *shtabentry;
216 : PgStat_StatTabEntry *tabentry;
217 19080 : Oid dboid = (shared ? InvalidOid : MyDatabaseId);
218 : TimestampTz ts;
219 :
220 19080 : if (!pgstat_track_counts)
221 0 : return;
222 :
223 : /* Store the data in the table's hash table entry. */
224 19080 : ts = GetCurrentTimestamp();
225 :
226 : /* block acquiring lock for the same reason as pgstat_report_autovac() */
227 19080 : entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION,
228 : dboid, tableoid, false);
229 :
230 19080 : shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats;
231 19080 : tabentry = &shtabentry->stats;
232 :
233 19080 : tabentry->live_tuples = livetuples;
234 19080 : tabentry->dead_tuples = deadtuples;
235 :
236 : /*
237 : * It is quite possible that a non-aggressive VACUUM ended up skipping
238 : * various pages, however, we'll zero the insert counter here regardless.
239 : * It's currently used only to track when we need to perform an "insert"
240 : * autovacuum, which are mainly intended to freeze newly inserted tuples.
241 : * Zeroing this may just mean we'll not try to vacuum the table again
242 : * until enough tuples have been inserted to trigger another insert
243 : * autovacuum. An anti-wraparound autovacuum will catch any persistent
244 : * stragglers.
245 : */
246 19080 : tabentry->ins_since_vacuum = 0;
247 :
248 19080 : if (AmAutoVacuumWorkerProcess())
249 : {
250 28 : tabentry->last_autovacuum_time = ts;
251 28 : tabentry->autovacuum_count++;
252 : }
253 : else
254 : {
255 19052 : tabentry->last_vacuum_time = ts;
256 19052 : tabentry->vacuum_count++;
257 : }
258 :
259 19080 : pgstat_unlock_entry(entry_ref);
260 :
261 : /*
262 : * Flush IO statistics now. pgstat_report_stat() will flush IO stats,
263 : * however this will not be called until after an entire autovacuum cycle
264 : * is done -- which will likely vacuum many relations -- or until the
265 : * VACUUM command has processed all tables and committed.
266 : */
267 19080 : pgstat_flush_io(false);
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 12228 : 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 12228 : Oid dboid = (rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId);
285 :
286 12228 : 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 12228 : if (pgstat_should_count_relation(rel) &&
302 12180 : rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
303 : {
304 : PgStat_TableXactStatus *trans;
305 :
306 11700 : for (trans = rel->pgstat_info->trans; trans; trans = trans->upper)
307 : {
308 146 : livetuples -= trans->tuples_inserted - trans->tuples_deleted;
309 146 : deadtuples -= trans->tuples_updated + trans->tuples_deleted;
310 : }
311 : /* count stuff inserted by already-aborted subxacts, too */
312 11554 : deadtuples -= rel->pgstat_info->counts.delta_dead_tuples;
313 : /* Since ANALYZE's counts are estimates, we could have underflowed */
314 11554 : livetuples = Max(livetuples, 0);
315 11554 : deadtuples = Max(deadtuples, 0);
316 : }
317 :
318 : /* block acquiring lock for the same reason as pgstat_report_autovac() */
319 12228 : entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, dboid,
320 : RelationGetRelid(rel),
321 : false);
322 : /* can't get dropped while accessed */
323 : Assert(entry_ref != NULL && entry_ref->shared_stats != NULL);
324 :
325 12228 : shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats;
326 12228 : tabentry = &shtabentry->stats;
327 :
328 12228 : tabentry->live_tuples = livetuples;
329 12228 : 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 12228 : if (resetcounter)
337 12184 : tabentry->mod_since_analyze = 0;
338 :
339 12228 : if (AmAutoVacuumWorkerProcess())
340 : {
341 68 : tabentry->last_autoanalyze_time = GetCurrentTimestamp();
342 68 : tabentry->autoanalyze_count++;
343 : }
344 : else
345 : {
346 12160 : tabentry->last_analyze_time = GetCurrentTimestamp();
347 12160 : tabentry->analyze_count++;
348 : }
349 :
350 12228 : pgstat_unlock_entry(entry_ref);
351 :
352 : /* see pgstat_report_vacuum() */
353 12228 : pgstat_flush_io(false);
354 : }
355 :
356 : /*
357 : * count a tuple insertion of n tuples
358 : */
359 : void
360 15726216 : pgstat_count_heap_insert(Relation rel, PgStat_Counter n)
361 : {
362 15726216 : if (pgstat_should_count_relation(rel))
363 : {
364 15433652 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
365 :
366 15433652 : ensure_tabstat_xact_level(pgstat_info);
367 15433652 : pgstat_info->trans->tuples_inserted += n;
368 : }
369 15726216 : }
370 :
371 : /*
372 : * count a tuple update
373 : */
374 : void
375 565596 : pgstat_count_heap_update(Relation rel, bool hot, bool newpage)
376 : {
377 : Assert(!(hot && newpage));
378 :
379 565596 : if (pgstat_should_count_relation(rel))
380 : {
381 565592 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
382 :
383 565592 : ensure_tabstat_xact_level(pgstat_info);
384 565592 : pgstat_info->trans->tuples_updated++;
385 :
386 : /*
387 : * tuples_hot_updated and tuples_newpage_updated counters are
388 : * nontransactional, so just advance them
389 : */
390 565592 : if (hot)
391 263864 : pgstat_info->counts.tuples_hot_updated++;
392 301728 : else if (newpage)
393 280486 : pgstat_info->counts.tuples_newpage_updated++;
394 : }
395 565596 : }
396 :
397 : /*
398 : * count a tuple deletion
399 : */
400 : void
401 2754930 : pgstat_count_heap_delete(Relation rel)
402 : {
403 2754930 : if (pgstat_should_count_relation(rel))
404 : {
405 2754930 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
406 :
407 2754930 : ensure_tabstat_xact_level(pgstat_info);
408 2754930 : pgstat_info->trans->tuples_deleted++;
409 : }
410 2754930 : }
411 :
412 : /*
413 : * update tuple counters due to truncate
414 : */
415 : void
416 2810 : pgstat_count_truncate(Relation rel)
417 : {
418 2810 : if (pgstat_should_count_relation(rel))
419 : {
420 2810 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
421 :
422 2810 : ensure_tabstat_xact_level(pgstat_info);
423 2810 : save_truncdrop_counters(pgstat_info->trans, false);
424 2810 : pgstat_info->trans->tuples_inserted = 0;
425 2810 : pgstat_info->trans->tuples_updated = 0;
426 2810 : pgstat_info->trans->tuples_deleted = 0;
427 : }
428 2810 : }
429 :
430 : /*
431 : * update dead-tuples count
432 : *
433 : * The semantics of this are that we are reporting the nontransactional
434 : * recovery of "delta" dead tuples; so delta_dead_tuples decreases
435 : * rather than increasing, and the change goes straight into the per-table
436 : * counter, not into transactional state.
437 : */
438 : void
439 32936 : pgstat_update_heap_dead_tuples(Relation rel, int delta)
440 : {
441 32936 : if (pgstat_should_count_relation(rel))
442 : {
443 32936 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
444 :
445 32936 : pgstat_info->counts.delta_dead_tuples -= delta;
446 : }
447 32936 : }
448 :
449 : /*
450 : * Support function for the SQL-callable pgstat* functions. Returns
451 : * the collected statistics for one table or NULL. NULL doesn't mean
452 : * that the table doesn't exist, just that there are no statistics, so the
453 : * caller is better off to report ZERO instead.
454 : */
455 : PgStat_StatTabEntry *
456 278776 : pgstat_fetch_stat_tabentry(Oid relid)
457 : {
458 278776 : return pgstat_fetch_stat_tabentry_ext(IsSharedRelation(relid), relid);
459 : }
460 :
461 : /*
462 : * More efficient version of pgstat_fetch_stat_tabentry(), allowing to specify
463 : * whether the to-be-accessed table is a shared relation or not.
464 : */
465 : PgStat_StatTabEntry *
466 283084 : pgstat_fetch_stat_tabentry_ext(bool shared, Oid reloid)
467 : {
468 283084 : Oid dboid = (shared ? InvalidOid : MyDatabaseId);
469 :
470 283084 : return (PgStat_StatTabEntry *)
471 283084 : pgstat_fetch_entry(PGSTAT_KIND_RELATION, dboid, reloid);
472 : }
473 :
474 : /*
475 : * find any existing PgStat_TableStatus entry for rel
476 : *
477 : * Find any existing PgStat_TableStatus entry for rel_id in the current
478 : * database. If not found, try finding from shared tables.
479 : *
480 : * If an entry is found, copy it and increment the copy's counters with their
481 : * subtransaction counterparts, then return the copy. The caller may need to
482 : * pfree() the copy.
483 : *
484 : * If no entry found, return NULL, don't create a new one.
485 : */
486 : PgStat_TableStatus *
487 48 : find_tabstat_entry(Oid rel_id)
488 : {
489 : PgStat_EntryRef *entry_ref;
490 : PgStat_TableXactStatus *trans;
491 48 : PgStat_TableStatus *tabentry = NULL;
492 48 : PgStat_TableStatus *tablestatus = NULL;
493 :
494 48 : entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_RELATION, MyDatabaseId, rel_id);
495 48 : if (!entry_ref)
496 : {
497 12 : entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_RELATION, InvalidOid, rel_id);
498 12 : if (!entry_ref)
499 12 : return tablestatus;
500 : }
501 :
502 36 : tabentry = (PgStat_TableStatus *) entry_ref->pending;
503 36 : tablestatus = palloc(sizeof(PgStat_TableStatus));
504 36 : *tablestatus = *tabentry;
505 :
506 : /*
507 : * Reset tablestatus->trans in the copy of PgStat_TableStatus as it may
508 : * point to a shared memory area. Its data is saved below, so removing it
509 : * does not matter.
510 : */
511 36 : tablestatus->trans = NULL;
512 :
513 : /*
514 : * Live subtransaction counts are not included yet. This is not a hot
515 : * code path so reconcile tuples_inserted, tuples_updated and
516 : * tuples_deleted even if the caller may not be interested in this data.
517 : */
518 84 : for (trans = tabentry->trans; trans != NULL; trans = trans->upper)
519 : {
520 48 : tablestatus->counts.tuples_inserted += trans->tuples_inserted;
521 48 : tablestatus->counts.tuples_updated += trans->tuples_updated;
522 48 : tablestatus->counts.tuples_deleted += trans->tuples_deleted;
523 : }
524 :
525 36 : return tablestatus;
526 : }
527 :
528 : /*
529 : * Perform relation stats specific end-of-transaction work. Helper for
530 : * AtEOXact_PgStat.
531 : *
532 : * Transfer transactional insert/update counts into the base tabstat entries.
533 : * We don't bother to free any of the transactional state, since it's all in
534 : * TopTransactionContext and will go away anyway.
535 : */
536 : void
537 223286 : AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit)
538 : {
539 : PgStat_TableXactStatus *trans;
540 :
541 835974 : for (trans = xact_state->first; trans != NULL; trans = trans->next)
542 : {
543 : PgStat_TableStatus *tabstat;
544 :
545 : Assert(trans->nest_level == 1);
546 : Assert(trans->upper == NULL);
547 612688 : tabstat = trans->parent;
548 : Assert(tabstat->trans == trans);
549 : /* restore pre-truncate/drop stats (if any) in case of aborted xact */
550 612688 : if (!isCommit)
551 19300 : restore_truncdrop_counters(trans);
552 : /* count attempted actions regardless of commit/abort */
553 612688 : tabstat->counts.tuples_inserted += trans->tuples_inserted;
554 612688 : tabstat->counts.tuples_updated += trans->tuples_updated;
555 612688 : tabstat->counts.tuples_deleted += trans->tuples_deleted;
556 612688 : if (isCommit)
557 : {
558 593388 : tabstat->counts.truncdropped = trans->truncdropped;
559 593388 : if (trans->truncdropped)
560 : {
561 : /* forget live/dead stats seen by backend thus far */
562 3338 : tabstat->counts.delta_live_tuples = 0;
563 3338 : tabstat->counts.delta_dead_tuples = 0;
564 : }
565 : /* insert adds a live tuple, delete removes one */
566 593388 : tabstat->counts.delta_live_tuples +=
567 593388 : trans->tuples_inserted - trans->tuples_deleted;
568 : /* update and delete each create a dead tuple */
569 593388 : tabstat->counts.delta_dead_tuples +=
570 593388 : trans->tuples_updated + trans->tuples_deleted;
571 : /* insert, update, delete each count as one change event */
572 593388 : tabstat->counts.changed_tuples +=
573 593388 : trans->tuples_inserted + trans->tuples_updated +
574 593388 : trans->tuples_deleted;
575 : }
576 : else
577 : {
578 : /* inserted tuples are dead, deleted tuples are unaffected */
579 19300 : tabstat->counts.delta_dead_tuples +=
580 19300 : trans->tuples_inserted + trans->tuples_updated;
581 : /* an aborted xact generates no changed_tuple events */
582 : }
583 612688 : tabstat->trans = NULL;
584 : }
585 223286 : }
586 :
587 : /*
588 : * Perform relation stats specific end-of-sub-transaction work. Helper for
589 : * AtEOSubXact_PgStat.
590 : *
591 : * Transfer transactional insert/update counts into the next higher
592 : * subtransaction state.
593 : */
594 : void
595 6594 : AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth)
596 : {
597 : PgStat_TableXactStatus *trans;
598 : PgStat_TableXactStatus *next_trans;
599 :
600 13800 : for (trans = xact_state->first; trans != NULL; trans = next_trans)
601 : {
602 : PgStat_TableStatus *tabstat;
603 :
604 7206 : next_trans = trans->next;
605 : Assert(trans->nest_level == nestDepth);
606 7206 : tabstat = trans->parent;
607 : Assert(tabstat->trans == trans);
608 :
609 7206 : if (isCommit)
610 : {
611 5690 : if (trans->upper && trans->upper->nest_level == nestDepth - 1)
612 : {
613 3448 : if (trans->truncdropped)
614 : {
615 : /* propagate the truncate/drop status one level up */
616 24 : save_truncdrop_counters(trans->upper, false);
617 : /* replace upper xact stats with ours */
618 24 : trans->upper->tuples_inserted = trans->tuples_inserted;
619 24 : trans->upper->tuples_updated = trans->tuples_updated;
620 24 : trans->upper->tuples_deleted = trans->tuples_deleted;
621 : }
622 : else
623 : {
624 3424 : trans->upper->tuples_inserted += trans->tuples_inserted;
625 3424 : trans->upper->tuples_updated += trans->tuples_updated;
626 3424 : trans->upper->tuples_deleted += trans->tuples_deleted;
627 : }
628 3448 : tabstat->trans = trans->upper;
629 3448 : pfree(trans);
630 : }
631 : else
632 : {
633 : /*
634 : * When there isn't an immediate parent state, we can just
635 : * reuse the record instead of going through a palloc/pfree
636 : * pushup (this works since it's all in TopTransactionContext
637 : * anyway). We have to re-link it into the parent level,
638 : * though, and that might mean pushing a new entry into the
639 : * pgStatXactStack.
640 : */
641 : PgStat_SubXactStatus *upper_xact_state;
642 :
643 2242 : upper_xact_state = pgstat_get_xact_stack_level(nestDepth - 1);
644 2242 : trans->next = upper_xact_state->first;
645 2242 : upper_xact_state->first = trans;
646 2242 : trans->nest_level = nestDepth - 1;
647 : }
648 : }
649 : else
650 : {
651 : /*
652 : * On abort, update top-level tabstat counts, then forget the
653 : * subtransaction
654 : */
655 :
656 : /* first restore values obliterated by truncate/drop */
657 1516 : restore_truncdrop_counters(trans);
658 : /* count attempted actions regardless of commit/abort */
659 1516 : tabstat->counts.tuples_inserted += trans->tuples_inserted;
660 1516 : tabstat->counts.tuples_updated += trans->tuples_updated;
661 1516 : tabstat->counts.tuples_deleted += trans->tuples_deleted;
662 : /* inserted tuples are dead, deleted tuples are unaffected */
663 1516 : tabstat->counts.delta_dead_tuples +=
664 1516 : trans->tuples_inserted + trans->tuples_updated;
665 1516 : tabstat->trans = trans->upper;
666 1516 : pfree(trans);
667 : }
668 : }
669 6594 : }
670 :
671 : /*
672 : * Generate 2PC records for all the pending transaction-dependent relation
673 : * stats.
674 : */
675 : void
676 714 : AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
677 : {
678 : PgStat_TableXactStatus *trans;
679 :
680 1576 : for (trans = xact_state->first; trans != NULL; trans = trans->next)
681 : {
682 : PgStat_TableStatus *tabstat PG_USED_FOR_ASSERTS_ONLY;
683 : TwoPhasePgStatRecord record;
684 :
685 : Assert(trans->nest_level == 1);
686 : Assert(trans->upper == NULL);
687 862 : tabstat = trans->parent;
688 : Assert(tabstat->trans == trans);
689 :
690 862 : record.tuples_inserted = trans->tuples_inserted;
691 862 : record.tuples_updated = trans->tuples_updated;
692 862 : record.tuples_deleted = trans->tuples_deleted;
693 862 : record.inserted_pre_truncdrop = trans->inserted_pre_truncdrop;
694 862 : record.updated_pre_truncdrop = trans->updated_pre_truncdrop;
695 862 : record.deleted_pre_truncdrop = trans->deleted_pre_truncdrop;
696 862 : record.id = tabstat->id;
697 862 : record.shared = tabstat->shared;
698 862 : record.truncdropped = trans->truncdropped;
699 :
700 862 : RegisterTwoPhaseRecord(TWOPHASE_RM_PGSTAT_ID, 0,
701 : &record, sizeof(TwoPhasePgStatRecord));
702 : }
703 714 : }
704 :
705 : /*
706 : * All we need do here is unlink the transaction stats state from the
707 : * nontransactional state. The nontransactional action counts will be
708 : * reported to the stats system immediately, while the effects on live and
709 : * dead tuple counts are preserved in the 2PC state file.
710 : *
711 : * Note: AtEOXact_PgStat_Relations is not called during PREPARE.
712 : */
713 : void
714 714 : PostPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
715 : {
716 : PgStat_TableXactStatus *trans;
717 :
718 1576 : for (trans = xact_state->first; trans != NULL; trans = trans->next)
719 : {
720 : PgStat_TableStatus *tabstat;
721 :
722 862 : tabstat = trans->parent;
723 862 : tabstat->trans = NULL;
724 : }
725 714 : }
726 :
727 : /*
728 : * 2PC processing routine for COMMIT PREPARED case.
729 : *
730 : * Load the saved counts into our local pgstats state.
731 : */
732 : void
733 762 : pgstat_twophase_postcommit(TransactionId xid, uint16 info,
734 : void *recdata, uint32 len)
735 : {
736 762 : TwoPhasePgStatRecord *rec = (TwoPhasePgStatRecord *) recdata;
737 : PgStat_TableStatus *pgstat_info;
738 :
739 : /* Find or create a tabstat entry for the rel */
740 762 : pgstat_info = pgstat_prep_relation_pending(rec->id, rec->shared);
741 :
742 : /* Same math as in AtEOXact_PgStat, commit case */
743 762 : pgstat_info->counts.tuples_inserted += rec->tuples_inserted;
744 762 : pgstat_info->counts.tuples_updated += rec->tuples_updated;
745 762 : pgstat_info->counts.tuples_deleted += rec->tuples_deleted;
746 762 : pgstat_info->counts.truncdropped = rec->truncdropped;
747 762 : if (rec->truncdropped)
748 : {
749 : /* forget live/dead stats seen by backend thus far */
750 4 : pgstat_info->counts.delta_live_tuples = 0;
751 4 : pgstat_info->counts.delta_dead_tuples = 0;
752 : }
753 762 : pgstat_info->counts.delta_live_tuples +=
754 762 : rec->tuples_inserted - rec->tuples_deleted;
755 762 : pgstat_info->counts.delta_dead_tuples +=
756 762 : rec->tuples_updated + rec->tuples_deleted;
757 762 : pgstat_info->counts.changed_tuples +=
758 762 : rec->tuples_inserted + rec->tuples_updated +
759 762 : rec->tuples_deleted;
760 762 : }
761 :
762 : /*
763 : * 2PC processing routine for ROLLBACK PREPARED case.
764 : *
765 : * Load the saved counts into our local pgstats state, but treat them
766 : * as aborted.
767 : */
768 : void
769 124 : pgstat_twophase_postabort(TransactionId xid, uint16 info,
770 : void *recdata, uint32 len)
771 : {
772 124 : TwoPhasePgStatRecord *rec = (TwoPhasePgStatRecord *) recdata;
773 : PgStat_TableStatus *pgstat_info;
774 :
775 : /* Find or create a tabstat entry for the rel */
776 124 : pgstat_info = pgstat_prep_relation_pending(rec->id, rec->shared);
777 :
778 : /* Same math as in AtEOXact_PgStat, abort case */
779 124 : if (rec->truncdropped)
780 : {
781 8 : rec->tuples_inserted = rec->inserted_pre_truncdrop;
782 8 : rec->tuples_updated = rec->updated_pre_truncdrop;
783 8 : rec->tuples_deleted = rec->deleted_pre_truncdrop;
784 : }
785 124 : pgstat_info->counts.tuples_inserted += rec->tuples_inserted;
786 124 : pgstat_info->counts.tuples_updated += rec->tuples_updated;
787 124 : pgstat_info->counts.tuples_deleted += rec->tuples_deleted;
788 124 : pgstat_info->counts.delta_dead_tuples +=
789 124 : rec->tuples_inserted + rec->tuples_updated;
790 124 : }
791 :
792 : /*
793 : * Flush out pending stats for the entry
794 : *
795 : * If nowait is true, this function returns false if lock could not
796 : * immediately acquired, otherwise true is returned.
797 : *
798 : * Some of the stats are copied to the corresponding pending database stats
799 : * entry when successfully flushing.
800 : */
801 : bool
802 1233380 : pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
803 : {
804 : static const PgStat_TableCounts all_zeroes;
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 1233380 : dboid = entry_ref->shared_entry->key.dboid;
812 1233380 : lstats = (PgStat_TableStatus *) entry_ref->pending;
813 1233380 : 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 1233380 : if (memcmp(&lstats->counts, &all_zeroes,
820 : sizeof(PgStat_TableCounts)) == 0)
821 : {
822 4102 : return true;
823 : }
824 :
825 1229278 : if (!pgstat_lock_entry(entry_ref, nowait))
826 0 : return false;
827 :
828 : /* add the values to the shared entry. */
829 1229278 : tabentry = &shtabstats->stats;
830 :
831 1229278 : tabentry->numscans += lstats->counts.numscans;
832 1229278 : if (lstats->counts.numscans)
833 : {
834 754986 : TimestampTz t = GetCurrentTransactionStopTimestamp();
835 :
836 754986 : if (t > tabentry->lastscan)
837 743068 : tabentry->lastscan = t;
838 : }
839 1229278 : tabentry->tuples_returned += lstats->counts.tuples_returned;
840 1229278 : tabentry->tuples_fetched += lstats->counts.tuples_fetched;
841 1229278 : tabentry->tuples_inserted += lstats->counts.tuples_inserted;
842 1229278 : tabentry->tuples_updated += lstats->counts.tuples_updated;
843 1229278 : tabentry->tuples_deleted += lstats->counts.tuples_deleted;
844 1229278 : tabentry->tuples_hot_updated += lstats->counts.tuples_hot_updated;
845 1229278 : tabentry->tuples_newpage_updated += lstats->counts.tuples_newpage_updated;
846 :
847 : /*
848 : * If table was truncated/dropped, first reset the live/dead counters.
849 : */
850 1229278 : if (lstats->counts.truncdropped)
851 : {
852 426 : tabentry->live_tuples = 0;
853 426 : tabentry->dead_tuples = 0;
854 426 : tabentry->ins_since_vacuum = 0;
855 : }
856 :
857 1229278 : tabentry->live_tuples += lstats->counts.delta_live_tuples;
858 1229278 : tabentry->dead_tuples += lstats->counts.delta_dead_tuples;
859 1229278 : tabentry->mod_since_analyze += lstats->counts.changed_tuples;
860 1229278 : tabentry->ins_since_vacuum += lstats->counts.tuples_inserted;
861 1229278 : tabentry->blocks_fetched += lstats->counts.blocks_fetched;
862 1229278 : tabentry->blocks_hit += lstats->counts.blocks_hit;
863 :
864 : /* Clamp live_tuples in case of negative delta_live_tuples */
865 1229278 : tabentry->live_tuples = Max(tabentry->live_tuples, 0);
866 : /* Likewise for dead_tuples */
867 1229278 : tabentry->dead_tuples = Max(tabentry->dead_tuples, 0);
868 :
869 1229278 : pgstat_unlock_entry(entry_ref);
870 :
871 : /* The entry was successfully flushed, add the same to database stats */
872 1229278 : dbentry = pgstat_prep_database_pending(dboid);
873 1229278 : dbentry->tuples_returned += lstats->counts.tuples_returned;
874 1229278 : dbentry->tuples_fetched += lstats->counts.tuples_fetched;
875 1229278 : dbentry->tuples_inserted += lstats->counts.tuples_inserted;
876 1229278 : dbentry->tuples_updated += lstats->counts.tuples_updated;
877 1229278 : dbentry->tuples_deleted += lstats->counts.tuples_deleted;
878 1229278 : dbentry->blocks_fetched += lstats->counts.blocks_fetched;
879 1229278 : dbentry->blocks_hit += lstats->counts.blocks_hit;
880 :
881 1229278 : return true;
882 : }
883 :
884 : void
885 1295124 : pgstat_relation_delete_pending_cb(PgStat_EntryRef *entry_ref)
886 : {
887 1295124 : PgStat_TableStatus *pending = (PgStat_TableStatus *) entry_ref->pending;
888 :
889 1295124 : if (pending->relation)
890 1170896 : pgstat_unlink_relation(pending->relation);
891 1295124 : }
892 :
893 : /*
894 : * Find or create a PgStat_TableStatus entry for rel. New entry is created and
895 : * initialized if not exists.
896 : */
897 : static PgStat_TableStatus *
898 1381160 : pgstat_prep_relation_pending(Oid rel_id, bool isshared)
899 : {
900 : PgStat_EntryRef *entry_ref;
901 : PgStat_TableStatus *pending;
902 :
903 1381160 : entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_RELATION,
904 : isshared ? InvalidOid : MyDatabaseId,
905 : rel_id, NULL);
906 1381160 : pending = entry_ref->pending;
907 1381160 : pending->id = rel_id;
908 1381160 : pending->shared = isshared;
909 :
910 1381160 : return pending;
911 : }
912 :
913 : /*
914 : * add a new (sub)transaction state record
915 : */
916 : static void
917 618514 : add_tabstat_xact_level(PgStat_TableStatus *pgstat_info, int nest_level)
918 : {
919 : PgStat_SubXactStatus *xact_state;
920 : PgStat_TableXactStatus *trans;
921 :
922 : /*
923 : * If this is the first rel to be modified at the current nest level, we
924 : * first have to push a transaction stack entry.
925 : */
926 618514 : xact_state = pgstat_get_xact_stack_level(nest_level);
927 :
928 : /* Now make a per-table stack entry */
929 : trans = (PgStat_TableXactStatus *)
930 618514 : MemoryContextAllocZero(TopTransactionContext,
931 : sizeof(PgStat_TableXactStatus));
932 618514 : trans->nest_level = nest_level;
933 618514 : trans->upper = pgstat_info->trans;
934 618514 : trans->parent = pgstat_info;
935 618514 : trans->next = xact_state->first;
936 618514 : xact_state->first = trans;
937 618514 : pgstat_info->trans = trans;
938 618514 : }
939 :
940 : /*
941 : * Add a new (sub)transaction record if needed.
942 : */
943 : static void
944 18756984 : ensure_tabstat_xact_level(PgStat_TableStatus *pgstat_info)
945 : {
946 18756984 : int nest_level = GetCurrentTransactionNestLevel();
947 :
948 18756984 : if (pgstat_info->trans == NULL ||
949 18143028 : pgstat_info->trans->nest_level != nest_level)
950 618514 : add_tabstat_xact_level(pgstat_info, nest_level);
951 18756984 : }
952 :
953 : /*
954 : * Whenever a table is truncated/dropped, we save its i/u/d counters so that
955 : * they can be cleared, and if the (sub)xact that executed the truncate/drop
956 : * later aborts, the counters can be restored to the saved (pre-truncate/drop)
957 : * values.
958 : *
959 : * Note that for truncate we do this on the first truncate in any particular
960 : * subxact level only.
961 : */
962 : static void
963 3700 : save_truncdrop_counters(PgStat_TableXactStatus *trans, bool is_drop)
964 : {
965 3700 : if (!trans->truncdropped || is_drop)
966 : {
967 3608 : trans->inserted_pre_truncdrop = trans->tuples_inserted;
968 3608 : trans->updated_pre_truncdrop = trans->tuples_updated;
969 3608 : trans->deleted_pre_truncdrop = trans->tuples_deleted;
970 3608 : trans->truncdropped = true;
971 : }
972 3700 : }
973 :
974 : /*
975 : * restore counters when a truncate aborts
976 : */
977 : static void
978 20816 : restore_truncdrop_counters(PgStat_TableXactStatus *trans)
979 : {
980 20816 : if (trans->truncdropped)
981 : {
982 214 : trans->tuples_inserted = trans->inserted_pre_truncdrop;
983 214 : trans->tuples_updated = trans->updated_pre_truncdrop;
984 214 : trans->tuples_deleted = trans->deleted_pre_truncdrop;
985 : }
986 20816 : }
|