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-2023, 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/partition.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 : #include "catalog/catalog.h"
29 :
30 :
31 : /* Record that's written to 2PC state file when pgstat state is persisted */
32 : typedef struct TwoPhasePgStatRecord
33 : {
34 : PgStat_Counter tuples_inserted; /* tuples inserted in xact */
35 : PgStat_Counter tuples_updated; /* tuples updated in xact */
36 : PgStat_Counter tuples_deleted; /* tuples deleted in xact */
37 : /* tuples i/u/d prior to truncate/drop */
38 : PgStat_Counter inserted_pre_truncdrop;
39 : PgStat_Counter updated_pre_truncdrop;
40 : PgStat_Counter deleted_pre_truncdrop;
41 : Oid id; /* table's OID */
42 : bool shared; /* is it a shared catalog? */
43 : bool truncdropped; /* was the relation truncated/dropped? */
44 : } TwoPhasePgStatRecord;
45 :
46 :
47 : static PgStat_TableStatus *pgstat_prep_relation_pending(Oid rel_id, bool isshared);
48 : static void add_tabstat_xact_level(PgStat_TableStatus *pgstat_info, int nest_level);
49 : static void ensure_tabstat_xact_level(PgStat_TableStatus *pgstat_info);
50 : static void save_truncdrop_counters(PgStat_TableXactStatus *trans, bool is_drop);
51 : static void restore_truncdrop_counters(PgStat_TableXactStatus *trans);
52 :
53 :
54 : /*
55 : * Copy stats between relations. This is used for things like REINDEX
56 : * CONCURRENTLY.
57 : */
58 : void
59 438 : pgstat_copy_relation_stats(Relation dst, Relation src)
60 : {
61 : PgStat_StatTabEntry *srcstats;
62 : PgStatShared_Relation *dstshstats;
63 : PgStat_EntryRef *dst_ref;
64 :
65 438 : srcstats = pgstat_fetch_stat_tabentry_ext(src->rd_rel->relisshared,
66 : RelationGetRelid(src));
67 438 : if (!srcstats)
68 234 : return;
69 :
70 204 : dst_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION,
71 204 : dst->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
72 : RelationGetRelid(dst),
73 : false);
74 :
75 204 : dstshstats = (PgStatShared_Relation *) dst_ref->shared_stats;
76 204 : dstshstats->stats = *srcstats;
77 :
78 204 : pgstat_unlock_entry(dst_ref);
79 : }
80 :
81 : /*
82 : * Initialize a relcache entry to count access statistics. Called whenever a
83 : * relation is opened.
84 : *
85 : * We assume that a relcache entry's pgstat_info field is zeroed by relcache.c
86 : * when the relcache entry is made; thereafter it is long-lived data.
87 : *
88 : * This does not create a reference to a stats entry in shared memory, nor
89 : * allocate memory for the pending stats. That happens in
90 : * pgstat_assoc_relation().
91 : */
92 : void
93 28123290 : pgstat_init_relation(Relation rel)
94 : {
95 28123290 : char relkind = rel->rd_rel->relkind;
96 :
97 : /*
98 : * We only count stats for relations with storage and partitioned tables
99 : */
100 28123290 : if (!RELKIND_HAS_STORAGE(relkind) && relkind != RELKIND_PARTITIONED_TABLE)
101 : {
102 112264 : rel->pgstat_enabled = false;
103 112264 : rel->pgstat_info = NULL;
104 112264 : return;
105 : }
106 :
107 28011026 : if (!pgstat_track_counts)
108 : {
109 360 : if (rel->pgstat_info)
110 20 : pgstat_unlink_relation(rel);
111 :
112 : /* We're not counting at all */
113 360 : rel->pgstat_enabled = false;
114 360 : rel->pgstat_info = NULL;
115 360 : return;
116 : }
117 :
118 28010666 : rel->pgstat_enabled = true;
119 : }
120 :
121 : /*
122 : * Prepare for statistics for this relation to be collected.
123 : *
124 : * This ensures we have a reference to the stats entry before stats can be
125 : * generated. That is important because a relation drop in another connection
126 : * could otherwise lead to the stats entry being dropped, which then later
127 : * would get recreated when flushing stats.
128 : *
129 : * This is separate from pgstat_init_relation() as it is not uncommon for
130 : * relcache entries to be opened without ever getting stats reported.
131 : */
132 : void
133 1283642 : pgstat_assoc_relation(Relation rel)
134 : {
135 : Assert(rel->pgstat_enabled);
136 : Assert(rel->pgstat_info == NULL);
137 :
138 : /* Else find or make the PgStat_TableStatus entry, and update link */
139 2567284 : rel->pgstat_info = pgstat_prep_relation_pending(RelationGetRelid(rel),
140 1283642 : rel->rd_rel->relisshared);
141 :
142 : /* don't allow link a stats to multiple relcache entries */
143 : Assert(rel->pgstat_info->relation == NULL);
144 :
145 : /* mark this relation as the owner */
146 1283642 : rel->pgstat_info->relation = rel;
147 1283642 : }
148 :
149 : /*
150 : * Break the mutual link between a relcache entry and pending stats entry.
151 : * This must be called whenever one end of the link is removed.
152 : */
153 : void
154 1991476 : pgstat_unlink_relation(Relation rel)
155 : {
156 : /* remove the link to stats info if any */
157 1991476 : if (rel->pgstat_info == NULL)
158 707834 : return;
159 :
160 : /* link sanity check */
161 : Assert(rel->pgstat_info->relation == rel);
162 1283642 : rel->pgstat_info->relation = NULL;
163 1283642 : rel->pgstat_info = NULL;
164 : }
165 :
166 : /*
167 : * Ensure that stats are dropped if transaction aborts.
168 : */
169 : void
170 105064 : pgstat_create_relation(Relation rel)
171 : {
172 105064 : pgstat_create_transactional(PGSTAT_KIND_RELATION,
173 105064 : rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
174 : RelationGetRelid(rel));
175 105064 : }
176 :
177 : /*
178 : * Ensure that stats are dropped if transaction commits.
179 : */
180 : void
181 59646 : pgstat_drop_relation(Relation rel)
182 : {
183 59646 : int nest_level = GetCurrentTransactionNestLevel();
184 : PgStat_TableStatus *pgstat_info;
185 :
186 59646 : pgstat_drop_transactional(PGSTAT_KIND_RELATION,
187 59646 : rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
188 : RelationGetRelid(rel));
189 :
190 59646 : if (!pgstat_should_count_relation(rel))
191 4032 : return;
192 :
193 : /*
194 : * Transactionally set counters to 0. That ensures that accesses to
195 : * pg_stat_xact_all_tables inside the transaction show 0.
196 : */
197 55614 : pgstat_info = rel->pgstat_info;
198 55614 : if (pgstat_info->trans &&
199 782 : pgstat_info->trans->nest_level == nest_level)
200 : {
201 776 : save_truncdrop_counters(pgstat_info->trans, true);
202 776 : pgstat_info->trans->tuples_inserted = 0;
203 776 : pgstat_info->trans->tuples_updated = 0;
204 776 : pgstat_info->trans->tuples_deleted = 0;
205 : }
206 : }
207 :
208 : /*
209 : * Report that the table was just vacuumed and flush IO statistics.
210 : */
211 : void
212 15868 : pgstat_report_vacuum(Oid tableoid, bool shared,
213 : PgStat_Counter livetuples, PgStat_Counter deadtuples)
214 : {
215 : PgStat_EntryRef *entry_ref;
216 : PgStatShared_Relation *shtabentry;
217 : PgStat_StatTabEntry *tabentry;
218 15868 : Oid dboid = (shared ? InvalidOid : MyDatabaseId);
219 : TimestampTz ts;
220 :
221 15868 : if (!pgstat_track_counts)
222 0 : return;
223 :
224 : /* Store the data in the table's hash table entry. */
225 15868 : ts = GetCurrentTimestamp();
226 :
227 : /* block acquiring lock for the same reason as pgstat_report_autovac() */
228 15868 : entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION,
229 : dboid, tableoid, false);
230 :
231 15868 : shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats;
232 15868 : tabentry = &shtabentry->stats;
233 :
234 15868 : tabentry->live_tuples = livetuples;
235 15868 : tabentry->dead_tuples = deadtuples;
236 :
237 : /*
238 : * It is quite possible that a non-aggressive VACUUM ended up skipping
239 : * various pages, however, we'll zero the insert counter here regardless.
240 : * It's currently used only to track when we need to perform an "insert"
241 : * autovacuum, which are mainly intended to freeze newly inserted tuples.
242 : * Zeroing this may just mean we'll not try to vacuum the table again
243 : * until enough tuples have been inserted to trigger another insert
244 : * autovacuum. An anti-wraparound autovacuum will catch any persistent
245 : * stragglers.
246 : */
247 15868 : tabentry->ins_since_vacuum = 0;
248 :
249 15868 : if (IsAutoVacuumWorkerProcess())
250 : {
251 192 : tabentry->last_autovacuum_time = ts;
252 192 : tabentry->autovacuum_count++;
253 : }
254 : else
255 : {
256 15676 : tabentry->last_vacuum_time = ts;
257 15676 : tabentry->vacuum_count++;
258 : }
259 :
260 15868 : pgstat_unlock_entry(entry_ref);
261 :
262 : /*
263 : * Flush IO statistics now. pgstat_report_stat() will flush IO stats,
264 : * however this will not be called until after an entire autovacuum cycle
265 : * is done -- which will likely vacuum many relations -- or until the
266 : * VACUUM command has processed all tables and committed.
267 : */
268 15868 : pgstat_flush_io(false);
269 : }
270 :
271 : /*
272 : * Report that the table was just analyzed and flush IO statistics.
273 : *
274 : * Caller must provide new live- and dead-tuples estimates, as well as a
275 : * flag indicating whether to reset the mod_since_analyze counter.
276 : */
277 : void
278 11128 : pgstat_report_analyze(Relation rel,
279 : PgStat_Counter livetuples, PgStat_Counter deadtuples,
280 : bool resetcounter)
281 : {
282 : PgStat_EntryRef *entry_ref;
283 : PgStatShared_Relation *shtabentry;
284 : PgStat_StatTabEntry *tabentry;
285 11128 : Oid dboid = (rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId);
286 :
287 11128 : if (!pgstat_track_counts)
288 0 : return;
289 :
290 : /*
291 : * Unlike VACUUM, ANALYZE might be running inside a transaction that has
292 : * already inserted and/or deleted rows in the target table. ANALYZE will
293 : * have counted such rows as live or dead respectively. Because we will
294 : * report our counts of such rows at transaction end, we should subtract
295 : * off these counts from the update we're making now, else they'll be
296 : * double-counted after commit. (This approach also ensures that the
297 : * shared stats entry ends up with the right numbers if we abort instead
298 : * of committing.)
299 : *
300 : * Waste no time on partitioned tables, though.
301 : */
302 11128 : if (pgstat_should_count_relation(rel) &&
303 11080 : rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
304 : {
305 : PgStat_TableXactStatus *trans;
306 :
307 10594 : for (trans = rel->pgstat_info->trans; trans; trans = trans->upper)
308 : {
309 140 : livetuples -= trans->tuples_inserted - trans->tuples_deleted;
310 140 : deadtuples -= trans->tuples_updated + trans->tuples_deleted;
311 : }
312 : /* count stuff inserted by already-aborted subxacts, too */
313 10454 : deadtuples -= rel->pgstat_info->counts.delta_dead_tuples;
314 : /* Since ANALYZE's counts are estimates, we could have underflowed */
315 10454 : livetuples = Max(livetuples, 0);
316 10454 : deadtuples = Max(deadtuples, 0);
317 : }
318 :
319 : /* block acquiring lock for the same reason as pgstat_report_autovac() */
320 11128 : entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, dboid,
321 : RelationGetRelid(rel),
322 : false);
323 : /* can't get dropped while accessed */
324 : Assert(entry_ref != NULL && entry_ref->shared_stats != NULL);
325 :
326 11128 : shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats;
327 11128 : tabentry = &shtabentry->stats;
328 :
329 11128 : tabentry->live_tuples = livetuples;
330 11128 : tabentry->dead_tuples = deadtuples;
331 :
332 : /*
333 : * If commanded, reset mod_since_analyze to zero. This forgets any
334 : * changes that were committed while the ANALYZE was in progress, but we
335 : * have no good way to estimate how many of those there were.
336 : */
337 11128 : if (resetcounter)
338 11084 : tabentry->mod_since_analyze = 0;
339 :
340 11128 : if (IsAutoVacuumWorkerProcess())
341 : {
342 388 : tabentry->last_autoanalyze_time = GetCurrentTimestamp();
343 388 : tabentry->autoanalyze_count++;
344 : }
345 : else
346 : {
347 10740 : tabentry->last_analyze_time = GetCurrentTimestamp();
348 10740 : tabentry->analyze_count++;
349 : }
350 :
351 11128 : pgstat_unlock_entry(entry_ref);
352 :
353 : /* see pgstat_report_vacuum() */
354 11128 : pgstat_flush_io(false);
355 : }
356 :
357 : /*
358 : * count a tuple insertion of n tuples
359 : */
360 : void
361 15390636 : pgstat_count_heap_insert(Relation rel, PgStat_Counter n)
362 : {
363 15390636 : if (pgstat_should_count_relation(rel))
364 : {
365 15157800 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
366 :
367 15157800 : ensure_tabstat_xact_level(pgstat_info);
368 15157800 : pgstat_info->trans->tuples_inserted += n;
369 : }
370 15390636 : }
371 :
372 : /*
373 : * count a tuple update
374 : */
375 : void
376 528424 : pgstat_count_heap_update(Relation rel, bool hot, bool newpage)
377 : {
378 : Assert(!(hot && newpage));
379 :
380 528424 : if (pgstat_should_count_relation(rel))
381 : {
382 528420 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
383 :
384 528420 : ensure_tabstat_xact_level(pgstat_info);
385 528420 : 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 528420 : if (hot)
392 238044 : pgstat_info->counts.tuples_hot_updated++;
393 290376 : else if (newpage)
394 272116 : pgstat_info->counts.tuples_newpage_updated++;
395 : }
396 528424 : }
397 :
398 : /*
399 : * count a tuple deletion
400 : */
401 : void
402 2676076 : pgstat_count_heap_delete(Relation rel)
403 : {
404 2676076 : if (pgstat_should_count_relation(rel))
405 : {
406 2676076 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
407 :
408 2676076 : ensure_tabstat_xact_level(pgstat_info);
409 2676076 : pgstat_info->trans->tuples_deleted++;
410 : }
411 2676076 : }
412 :
413 : /*
414 : * update tuple counters due to truncate
415 : */
416 : void
417 2584 : pgstat_count_truncate(Relation rel)
418 : {
419 2584 : if (pgstat_should_count_relation(rel))
420 : {
421 2584 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
422 :
423 2584 : ensure_tabstat_xact_level(pgstat_info);
424 2584 : save_truncdrop_counters(pgstat_info->trans, false);
425 2584 : pgstat_info->trans->tuples_inserted = 0;
426 2584 : pgstat_info->trans->tuples_updated = 0;
427 2584 : pgstat_info->trans->tuples_deleted = 0;
428 : }
429 2584 : }
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 27420 : pgstat_update_heap_dead_tuples(Relation rel, int delta)
441 : {
442 27420 : if (pgstat_should_count_relation(rel))
443 : {
444 27420 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
445 :
446 27420 : pgstat_info->counts.delta_dead_tuples -= delta;
447 : }
448 27420 : }
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 8564 : pgstat_fetch_stat_tabentry(Oid relid)
458 : {
459 8564 : 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 13760 : pgstat_fetch_stat_tabentry_ext(bool shared, Oid reloid)
468 : {
469 13760 : Oid dboid = (shared ? InvalidOid : MyDatabaseId);
470 :
471 13760 : return (PgStat_StatTabEntry *)
472 13760 : 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 195312 : AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit)
539 : {
540 : PgStat_TableXactStatus *trans;
541 :
542 754230 : 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 558918 : tabstat = trans->parent;
549 : Assert(tabstat->trans == trans);
550 : /* restore pre-truncate/drop stats (if any) in case of aborted xact */
551 558918 : if (!isCommit)
552 17228 : restore_truncdrop_counters(trans);
553 : /* count attempted actions regardless of commit/abort */
554 558918 : tabstat->counts.tuples_inserted += trans->tuples_inserted;
555 558918 : tabstat->counts.tuples_updated += trans->tuples_updated;
556 558918 : tabstat->counts.tuples_deleted += trans->tuples_deleted;
557 558918 : if (isCommit)
558 : {
559 541690 : tabstat->counts.truncdropped = trans->truncdropped;
560 541690 : if (trans->truncdropped)
561 : {
562 : /* forget live/dead stats seen by backend thus far */
563 3034 : tabstat->counts.delta_live_tuples = 0;
564 3034 : tabstat->counts.delta_dead_tuples = 0;
565 : }
566 : /* insert adds a live tuple, delete removes one */
567 541690 : tabstat->counts.delta_live_tuples +=
568 541690 : trans->tuples_inserted - trans->tuples_deleted;
569 : /* update and delete each create a dead tuple */
570 541690 : tabstat->counts.delta_dead_tuples +=
571 541690 : trans->tuples_updated + trans->tuples_deleted;
572 : /* insert, update, delete each count as one change event */
573 541690 : tabstat->counts.changed_tuples +=
574 541690 : trans->tuples_inserted + trans->tuples_updated +
575 541690 : trans->tuples_deleted;
576 : }
577 : else
578 : {
579 : /* inserted tuples are dead, deleted tuples are unaffected */
580 17228 : tabstat->counts.delta_dead_tuples +=
581 17228 : trans->tuples_inserted + trans->tuples_updated;
582 : /* an aborted xact generates no changed_tuple events */
583 : }
584 558918 : tabstat->trans = NULL;
585 : }
586 195312 : }
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 6646 : AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth)
597 : {
598 : PgStat_TableXactStatus *trans;
599 : PgStat_TableXactStatus *next_trans;
600 :
601 13900 : for (trans = xact_state->first; trans != NULL; trans = next_trans)
602 : {
603 : PgStat_TableStatus *tabstat;
604 :
605 7254 : next_trans = trans->next;
606 : Assert(trans->nest_level == nestDepth);
607 7254 : tabstat = trans->parent;
608 : Assert(tabstat->trans == trans);
609 :
610 7254 : if (isCommit)
611 : {
612 5738 : if (trans->upper && trans->upper->nest_level == nestDepth - 1)
613 : {
614 3460 : 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 3436 : trans->upper->tuples_inserted += trans->tuples_inserted;
626 3436 : trans->upper->tuples_updated += trans->tuples_updated;
627 3436 : trans->upper->tuples_deleted += trans->tuples_deleted;
628 : }
629 3460 : tabstat->trans = trans->upper;
630 3460 : 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 2278 : upper_xact_state = pgstat_get_xact_stack_level(nestDepth - 1);
645 2278 : trans->next = upper_xact_state->first;
646 2278 : upper_xact_state->first = trans;
647 2278 : 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 1516 : restore_truncdrop_counters(trans);
659 : /* count attempted actions regardless of commit/abort */
660 1516 : tabstat->counts.tuples_inserted += trans->tuples_inserted;
661 1516 : tabstat->counts.tuples_updated += trans->tuples_updated;
662 1516 : tabstat->counts.tuples_deleted += trans->tuples_deleted;
663 : /* inserted tuples are dead, deleted tuples are unaffected */
664 1516 : tabstat->counts.delta_dead_tuples +=
665 1516 : trans->tuples_inserted + trans->tuples_updated;
666 1516 : tabstat->trans = trans->upper;
667 1516 : pfree(trans);
668 : }
669 : }
670 6646 : }
671 :
672 : /*
673 : * Generate 2PC records for all the pending transaction-dependent relation
674 : * stats.
675 : */
676 : void
677 732 : AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
678 : {
679 : PgStat_TableXactStatus *trans;
680 :
681 1626 : 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 894 : tabstat = trans->parent;
689 : Assert(tabstat->trans == trans);
690 :
691 894 : record.tuples_inserted = trans->tuples_inserted;
692 894 : record.tuples_updated = trans->tuples_updated;
693 894 : record.tuples_deleted = trans->tuples_deleted;
694 894 : record.inserted_pre_truncdrop = trans->inserted_pre_truncdrop;
695 894 : record.updated_pre_truncdrop = trans->updated_pre_truncdrop;
696 894 : record.deleted_pre_truncdrop = trans->deleted_pre_truncdrop;
697 894 : record.id = tabstat->id;
698 894 : record.shared = tabstat->shared;
699 894 : record.truncdropped = trans->truncdropped;
700 :
701 894 : RegisterTwoPhaseRecord(TWOPHASE_RM_PGSTAT_ID, 0,
702 : &record, sizeof(TwoPhasePgStatRecord));
703 : }
704 732 : }
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 732 : PostPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
716 : {
717 : PgStat_TableXactStatus *trans;
718 :
719 1626 : for (trans = xact_state->first; trans != NULL; trans = trans->next)
720 : {
721 : PgStat_TableStatus *tabstat;
722 :
723 894 : tabstat = trans->parent;
724 894 : tabstat->trans = NULL;
725 : }
726 732 : }
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 772 : pgstat_twophase_postcommit(TransactionId xid, uint16 info,
735 : void *recdata, uint32 len)
736 : {
737 772 : TwoPhasePgStatRecord *rec = (TwoPhasePgStatRecord *) recdata;
738 : PgStat_TableStatus *pgstat_info;
739 :
740 : /* Find or create a tabstat entry for the rel */
741 772 : pgstat_info = pgstat_prep_relation_pending(rec->id, rec->shared);
742 :
743 : /* Same math as in AtEOXact_PgStat, commit case */
744 772 : pgstat_info->counts.tuples_inserted += rec->tuples_inserted;
745 772 : pgstat_info->counts.tuples_updated += rec->tuples_updated;
746 772 : pgstat_info->counts.tuples_deleted += rec->tuples_deleted;
747 772 : pgstat_info->counts.truncdropped = rec->truncdropped;
748 772 : 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 772 : pgstat_info->counts.delta_live_tuples +=
755 772 : rec->tuples_inserted - rec->tuples_deleted;
756 772 : pgstat_info->counts.delta_dead_tuples +=
757 772 : rec->tuples_updated + rec->tuples_deleted;
758 772 : pgstat_info->counts.changed_tuples +=
759 772 : rec->tuples_inserted + rec->tuples_updated +
760 772 : rec->tuples_deleted;
761 772 : }
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 1150722 : pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
804 : {
805 : static const PgStat_TableCounts all_zeroes;
806 : Oid dboid;
807 : PgStat_TableStatus *lstats; /* pending stats entry */
808 : PgStatShared_Relation *shtabstats;
809 : PgStat_StatTabEntry *tabentry; /* table entry of shared stats */
810 : PgStat_StatDBEntry *dbentry; /* pending database entry */
811 :
812 1150722 : dboid = entry_ref->shared_entry->key.dboid;
813 1150722 : lstats = (PgStat_TableStatus *) entry_ref->pending;
814 1150722 : shtabstats = (PgStatShared_Relation *) entry_ref->shared_stats;
815 :
816 : /*
817 : * Ignore entries that didn't accumulate any actual counts, such as
818 : * indexes that were opened by the planner but not used.
819 : */
820 1150722 : if (memcmp(&lstats->counts, &all_zeroes,
821 : sizeof(PgStat_TableCounts)) == 0)
822 : {
823 3538 : return true;
824 : }
825 :
826 1147184 : if (!pgstat_lock_entry(entry_ref, nowait))
827 0 : return false;
828 :
829 : /* add the values to the shared entry. */
830 1147184 : tabentry = &shtabstats->stats;
831 :
832 1147184 : tabentry->numscans += lstats->counts.numscans;
833 1147184 : if (lstats->counts.numscans)
834 : {
835 705366 : TimestampTz t = GetCurrentTransactionStopTimestamp();
836 :
837 705366 : if (t > tabentry->lastscan)
838 693586 : tabentry->lastscan = t;
839 : }
840 1147184 : tabentry->tuples_returned += lstats->counts.tuples_returned;
841 1147184 : tabentry->tuples_fetched += lstats->counts.tuples_fetched;
842 1147184 : tabentry->tuples_inserted += lstats->counts.tuples_inserted;
843 1147184 : tabentry->tuples_updated += lstats->counts.tuples_updated;
844 1147184 : tabentry->tuples_deleted += lstats->counts.tuples_deleted;
845 1147184 : tabentry->tuples_hot_updated += lstats->counts.tuples_hot_updated;
846 1147184 : tabentry->tuples_newpage_updated += lstats->counts.tuples_newpage_updated;
847 :
848 : /*
849 : * If table was truncated/dropped, first reset the live/dead counters.
850 : */
851 1147184 : if (lstats->counts.truncdropped)
852 : {
853 414 : tabentry->live_tuples = 0;
854 414 : tabentry->dead_tuples = 0;
855 414 : tabentry->ins_since_vacuum = 0;
856 : }
857 :
858 1147184 : tabentry->live_tuples += lstats->counts.delta_live_tuples;
859 1147184 : tabentry->dead_tuples += lstats->counts.delta_dead_tuples;
860 1147184 : tabentry->mod_since_analyze += lstats->counts.changed_tuples;
861 1147184 : tabentry->ins_since_vacuum += lstats->counts.tuples_inserted;
862 1147184 : tabentry->blocks_fetched += lstats->counts.blocks_fetched;
863 1147184 : tabentry->blocks_hit += lstats->counts.blocks_hit;
864 :
865 : /* Clamp live_tuples in case of negative delta_live_tuples */
866 1147184 : tabentry->live_tuples = Max(tabentry->live_tuples, 0);
867 : /* Likewise for dead_tuples */
868 1147184 : tabentry->dead_tuples = Max(tabentry->dead_tuples, 0);
869 :
870 1147184 : pgstat_unlock_entry(entry_ref);
871 :
872 : /* The entry was successfully flushed, add the same to database stats */
873 1147184 : dbentry = pgstat_prep_database_pending(dboid);
874 1147184 : dbentry->tuples_returned += lstats->counts.tuples_returned;
875 1147184 : dbentry->tuples_fetched += lstats->counts.tuples_fetched;
876 1147184 : dbentry->tuples_inserted += lstats->counts.tuples_inserted;
877 1147184 : dbentry->tuples_updated += lstats->counts.tuples_updated;
878 1147184 : dbentry->tuples_deleted += lstats->counts.tuples_deleted;
879 1147184 : dbentry->blocks_fetched += lstats->counts.blocks_fetched;
880 1147184 : dbentry->blocks_hit += lstats->counts.blocks_hit;
881 :
882 1147184 : return true;
883 : }
884 :
885 : void
886 1206896 : pgstat_relation_delete_pending_cb(PgStat_EntryRef *entry_ref)
887 : {
888 1206896 : PgStat_TableStatus *pending = (PgStat_TableStatus *) entry_ref->pending;
889 :
890 1206896 : if (pending->relation)
891 1091524 : pgstat_unlink_relation(pending->relation);
892 1206896 : }
893 :
894 : /*
895 : * Find or create a PgStat_TableStatus entry for rel. New entry is created and
896 : * initialized if not exists.
897 : */
898 : static PgStat_TableStatus *
899 1284538 : pgstat_prep_relation_pending(Oid rel_id, bool isshared)
900 : {
901 : PgStat_EntryRef *entry_ref;
902 : PgStat_TableStatus *pending;
903 :
904 1284538 : entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_RELATION,
905 : isshared ? InvalidOid : MyDatabaseId,
906 : rel_id, NULL);
907 1284538 : pending = entry_ref->pending;
908 1284538 : pending->id = rel_id;
909 1284538 : pending->shared = isshared;
910 :
911 1284538 : return pending;
912 : }
913 :
914 : /*
915 : * add a new (sub)transaction state record
916 : */
917 : static void
918 564788 : add_tabstat_xact_level(PgStat_TableStatus *pgstat_info, int nest_level)
919 : {
920 : PgStat_SubXactStatus *xact_state;
921 : PgStat_TableXactStatus *trans;
922 :
923 : /*
924 : * If this is the first rel to be modified at the current nest level, we
925 : * first have to push a transaction stack entry.
926 : */
927 564788 : xact_state = pgstat_get_xact_stack_level(nest_level);
928 :
929 : /* Now make a per-table stack entry */
930 : trans = (PgStat_TableXactStatus *)
931 564788 : MemoryContextAllocZero(TopTransactionContext,
932 : sizeof(PgStat_TableXactStatus));
933 564788 : trans->nest_level = nest_level;
934 564788 : trans->upper = pgstat_info->trans;
935 564788 : trans->parent = pgstat_info;
936 564788 : trans->next = xact_state->first;
937 564788 : xact_state->first = trans;
938 564788 : pgstat_info->trans = trans;
939 564788 : }
940 :
941 : /*
942 : * Add a new (sub)transaction record if needed.
943 : */
944 : static void
945 18364880 : ensure_tabstat_xact_level(PgStat_TableStatus *pgstat_info)
946 : {
947 18364880 : int nest_level = GetCurrentTransactionNestLevel();
948 :
949 18364880 : if (pgstat_info->trans == NULL ||
950 17804664 : pgstat_info->trans->nest_level != nest_level)
951 564788 : add_tabstat_xact_level(pgstat_info, nest_level);
952 18364880 : }
953 :
954 : /*
955 : * Whenever a table is truncated/dropped, we save its i/u/d counters so that
956 : * they can be cleared, and if the (sub)xact that executed the truncate/drop
957 : * later aborts, the counters can be restored to the saved (pre-truncate/drop)
958 : * values.
959 : *
960 : * Note that for truncate we do this on the first truncate in any particular
961 : * subxact level only.
962 : */
963 : static void
964 3384 : save_truncdrop_counters(PgStat_TableXactStatus *trans, bool is_drop)
965 : {
966 3384 : if (!trans->truncdropped || is_drop)
967 : {
968 3292 : trans->inserted_pre_truncdrop = trans->tuples_inserted;
969 3292 : trans->updated_pre_truncdrop = trans->tuples_updated;
970 3292 : trans->deleted_pre_truncdrop = trans->tuples_deleted;
971 3292 : trans->truncdropped = true;
972 : }
973 3384 : }
974 :
975 : /*
976 : * restore counters when a truncate aborts
977 : */
978 : static void
979 18744 : restore_truncdrop_counters(PgStat_TableXactStatus *trans)
980 : {
981 18744 : if (trans->truncdropped)
982 : {
983 202 : trans->tuples_inserted = trans->inserted_pre_truncdrop;
984 202 : trans->tuples_updated = trans->updated_pre_truncdrop;
985 202 : trans->tuples_deleted = trans->deleted_pre_truncdrop;
986 : }
987 18744 : }
|