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 432 : pgstat_copy_relation_stats(Relation dst, Relation src)
60 : {
61 : PgStat_StatTabEntry *srcstats;
62 : PgStatShared_Relation *dstshstats;
63 : PgStat_EntryRef *dst_ref;
64 :
65 432 : srcstats = pgstat_fetch_stat_tabentry_ext(src->rd_rel->relisshared,
66 : RelationGetRelid(src));
67 432 : if (!srcstats)
68 234 : return;
69 :
70 198 : dst_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION,
71 198 : dst->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
72 : RelationGetRelid(dst),
73 : false);
74 :
75 198 : dstshstats = (PgStatShared_Relation *) dst_ref->shared_stats;
76 198 : dstshstats->stats = *srcstats;
77 :
78 198 : 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 53026930 : pgstat_init_relation(Relation rel)
94 : {
95 53026930 : char relkind = rel->rd_rel->relkind;
96 :
97 : /*
98 : * We only count stats for relations with storage and partitioned tables
99 : */
100 53026930 : if (!RELKIND_HAS_STORAGE(relkind) && relkind != RELKIND_PARTITIONED_TABLE)
101 : {
102 314310 : rel->pgstat_enabled = false;
103 314310 : rel->pgstat_info = NULL;
104 314310 : return;
105 : }
106 :
107 52712620 : 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 52712260 : 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 1482732 : 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 2965464 : rel->pgstat_info = pgstat_prep_relation_pending(RelationGetRelid(rel),
140 1482732 : 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 1482732 : rel->pgstat_info->relation = rel;
147 1482732 : }
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 2677584 : pgstat_unlink_relation(Relation rel)
155 : {
156 : /* remove the link to stats info if any */
157 2677584 : if (rel->pgstat_info == NULL)
158 1194852 : return;
159 :
160 : /* link sanity check */
161 : Assert(rel->pgstat_info->relation == rel);
162 1482732 : rel->pgstat_info->relation = NULL;
163 1482732 : rel->pgstat_info = NULL;
164 : }
165 :
166 : /*
167 : * Ensure that stats are dropped if transaction aborts.
168 : */
169 : void
170 327308 : pgstat_create_relation(Relation rel)
171 : {
172 327308 : pgstat_create_transactional(PGSTAT_KIND_RELATION,
173 327308 : rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
174 : RelationGetRelid(rel));
175 327308 : }
176 :
177 : /*
178 : * Ensure that stats are dropped if transaction commits.
179 : */
180 : void
181 58834 : pgstat_drop_relation(Relation rel)
182 : {
183 58834 : int nest_level = GetCurrentTransactionNestLevel();
184 : PgStat_TableStatus *pgstat_info;
185 :
186 58834 : pgstat_drop_transactional(PGSTAT_KIND_RELATION,
187 58834 : rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
188 : RelationGetRelid(rel));
189 :
190 58834 : if (!pgstat_should_count_relation(rel))
191 3900 : 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 54934 : pgstat_info = rel->pgstat_info;
198 54934 : if (pgstat_info->trans &&
199 776 : pgstat_info->trans->nest_level == nest_level)
200 : {
201 770 : save_truncdrop_counters(pgstat_info->trans, true);
202 770 : pgstat_info->trans->tuples_inserted = 0;
203 770 : pgstat_info->trans->tuples_updated = 0;
204 770 : 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 73498 : 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 73498 : Oid dboid = (shared ? InvalidOid : MyDatabaseId);
219 : TimestampTz ts;
220 :
221 73498 : if (!pgstat_track_counts)
222 0 : return;
223 :
224 : /* Store the data in the table's hash table entry. */
225 73498 : ts = GetCurrentTimestamp();
226 :
227 : /* block acquiring lock for the same reason as pgstat_report_autovac() */
228 73498 : entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION,
229 : dboid, tableoid, false);
230 :
231 73498 : shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats;
232 73498 : tabentry = &shtabentry->stats;
233 :
234 73498 : tabentry->live_tuples = livetuples;
235 73498 : 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 73498 : tabentry->ins_since_vacuum = 0;
248 :
249 73498 : if (IsAutoVacuumWorkerProcess())
250 : {
251 188 : tabentry->last_autovacuum_time = ts;
252 188 : tabentry->autovacuum_count++;
253 : }
254 : else
255 : {
256 73310 : tabentry->last_vacuum_time = ts;
257 73310 : tabentry->vacuum_count++;
258 : }
259 :
260 73498 : 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 73498 : 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 47214 : 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 47214 : Oid dboid = (rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId);
286 :
287 47214 : 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 47214 : if (pgstat_should_count_relation(rel) &&
303 47166 : rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
304 : {
305 : PgStat_TableXactStatus *trans;
306 :
307 46680 : 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 46540 : deadtuples -= rel->pgstat_info->counts.delta_dead_tuples;
314 : /* Since ANALYZE's counts are estimates, we could have underflowed */
315 46540 : livetuples = Max(livetuples, 0);
316 46540 : deadtuples = Max(deadtuples, 0);
317 : }
318 :
319 : /* block acquiring lock for the same reason as pgstat_report_autovac() */
320 47214 : 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 47214 : shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats;
327 47214 : tabentry = &shtabentry->stats;
328 :
329 47214 : tabentry->live_tuples = livetuples;
330 47214 : 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 47214 : if (resetcounter)
338 47170 : tabentry->mod_since_analyze = 0;
339 :
340 47214 : if (IsAutoVacuumWorkerProcess())
341 : {
342 338 : tabentry->last_autoanalyze_time = GetCurrentTimestamp();
343 338 : tabentry->autoanalyze_count++;
344 : }
345 : else
346 : {
347 46876 : tabentry->last_analyze_time = GetCurrentTimestamp();
348 46876 : tabentry->analyze_count++;
349 : }
350 :
351 47214 : pgstat_unlock_entry(entry_ref);
352 :
353 : /* see pgstat_report_vacuum() */
354 47214 : pgstat_flush_io(false);
355 : }
356 :
357 : /*
358 : * count a tuple insertion of n tuples
359 : */
360 : void
361 24769226 : pgstat_count_heap_insert(Relation rel, PgStat_Counter n)
362 : {
363 24769226 : if (pgstat_should_count_relation(rel))
364 : {
365 22573684 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
366 :
367 22573684 : ensure_tabstat_xact_level(pgstat_info);
368 22573684 : pgstat_info->trans->tuples_inserted += n;
369 : }
370 24769226 : }
371 :
372 : /*
373 : * count a tuple update
374 : */
375 : void
376 832974 : pgstat_count_heap_update(Relation rel, bool hot, bool newpage)
377 : {
378 : Assert(!(hot && newpage));
379 :
380 832974 : if (pgstat_should_count_relation(rel))
381 : {
382 832970 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
383 :
384 832970 : ensure_tabstat_xact_level(pgstat_info);
385 832970 : 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 832970 : if (hot)
392 427444 : pgstat_info->counts.tuples_hot_updated++;
393 405526 : else if (newpage)
394 387446 : pgstat_info->counts.tuples_newpage_updated++;
395 : }
396 832974 : }
397 :
398 : /*
399 : * count a tuple deletion
400 : */
401 : void
402 2827432 : pgstat_count_heap_delete(Relation rel)
403 : {
404 2827432 : if (pgstat_should_count_relation(rel))
405 : {
406 2827432 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
407 :
408 2827432 : ensure_tabstat_xact_level(pgstat_info);
409 2827432 : pgstat_info->trans->tuples_deleted++;
410 : }
411 2827432 : }
412 :
413 : /*
414 : * update tuple counters due to truncate
415 : */
416 : void
417 2564 : pgstat_count_truncate(Relation rel)
418 : {
419 2564 : if (pgstat_should_count_relation(rel))
420 : {
421 2564 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
422 :
423 2564 : ensure_tabstat_xact_level(pgstat_info);
424 2564 : save_truncdrop_counters(pgstat_info->trans, false);
425 2564 : pgstat_info->trans->tuples_inserted = 0;
426 2564 : pgstat_info->trans->tuples_updated = 0;
427 2564 : pgstat_info->trans->tuples_deleted = 0;
428 : }
429 2564 : }
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 110058 : pgstat_update_heap_dead_tuples(Relation rel, int delta)
441 : {
442 110058 : if (pgstat_should_count_relation(rel))
443 : {
444 110056 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
445 :
446 110056 : pgstat_info->counts.delta_dead_tuples -= delta;
447 : }
448 110058 : }
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 8316 : pgstat_fetch_stat_tabentry(Oid relid)
458 : {
459 8316 : 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 12858 : pgstat_fetch_stat_tabentry_ext(bool shared, Oid reloid)
468 : {
469 12858 : Oid dboid = (shared ? InvalidOid : MyDatabaseId);
470 :
471 12858 : return (PgStat_StatTabEntry *)
472 12858 : 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 no entry found, return NULL, don't create a new one
482 : */
483 : PgStat_TableStatus *
484 48 : find_tabstat_entry(Oid rel_id)
485 : {
486 : PgStat_EntryRef *entry_ref;
487 :
488 48 : entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_RELATION, MyDatabaseId, rel_id);
489 48 : if (!entry_ref)
490 12 : entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_RELATION, InvalidOid, rel_id);
491 :
492 48 : if (entry_ref)
493 36 : return entry_ref->pending;
494 12 : return NULL;
495 : }
496 :
497 : /*
498 : * Perform relation stats specific end-of-transaction work. Helper for
499 : * AtEOXact_PgStat.
500 : *
501 : * Transfer transactional insert/update counts into the base tabstat entries.
502 : * We don't bother to free any of the transactional state, since it's all in
503 : * TopTransactionContext and will go away anyway.
504 : */
505 : void
506 586418 : AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit)
507 : {
508 : PgStat_TableXactStatus *trans;
509 :
510 1997344 : for (trans = xact_state->first; trans != NULL; trans = trans->next)
511 : {
512 : PgStat_TableStatus *tabstat;
513 :
514 : Assert(trans->nest_level == 1);
515 : Assert(trans->upper == NULL);
516 1410926 : tabstat = trans->parent;
517 : Assert(tabstat->trans == trans);
518 : /* restore pre-truncate/drop stats (if any) in case of aborted xact */
519 1410926 : if (!isCommit)
520 16624 : restore_truncdrop_counters(trans);
521 : /* count attempted actions regardless of commit/abort */
522 1410926 : tabstat->counts.tuples_inserted += trans->tuples_inserted;
523 1410926 : tabstat->counts.tuples_updated += trans->tuples_updated;
524 1410926 : tabstat->counts.tuples_deleted += trans->tuples_deleted;
525 1410926 : if (isCommit)
526 : {
527 1394302 : tabstat->counts.truncdropped = trans->truncdropped;
528 1394302 : if (trans->truncdropped)
529 : {
530 : /* forget live/dead stats seen by backend thus far */
531 3008 : tabstat->counts.delta_live_tuples = 0;
532 3008 : tabstat->counts.delta_dead_tuples = 0;
533 : }
534 : /* insert adds a live tuple, delete removes one */
535 1394302 : tabstat->counts.delta_live_tuples +=
536 1394302 : trans->tuples_inserted - trans->tuples_deleted;
537 : /* update and delete each create a dead tuple */
538 1394302 : tabstat->counts.delta_dead_tuples +=
539 1394302 : trans->tuples_updated + trans->tuples_deleted;
540 : /* insert, update, delete each count as one change event */
541 1394302 : tabstat->counts.changed_tuples +=
542 1394302 : trans->tuples_inserted + trans->tuples_updated +
543 1394302 : trans->tuples_deleted;
544 : }
545 : else
546 : {
547 : /* inserted tuples are dead, deleted tuples are unaffected */
548 16624 : tabstat->counts.delta_dead_tuples +=
549 16624 : trans->tuples_inserted + trans->tuples_updated;
550 : /* an aborted xact generates no changed_tuple events */
551 : }
552 1410926 : tabstat->trans = NULL;
553 : }
554 586418 : }
555 :
556 : /*
557 : * Perform relation stats specific end-of-sub-transaction work. Helper for
558 : * AtEOSubXact_PgStat.
559 : *
560 : * Transfer transactional insert/update counts into the next higher
561 : * subtransaction state.
562 : */
563 : void
564 6630 : AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth)
565 : {
566 : PgStat_TableXactStatus *trans;
567 : PgStat_TableXactStatus *next_trans;
568 :
569 13868 : for (trans = xact_state->first; trans != NULL; trans = next_trans)
570 : {
571 : PgStat_TableStatus *tabstat;
572 :
573 7238 : next_trans = trans->next;
574 : Assert(trans->nest_level == nestDepth);
575 7238 : tabstat = trans->parent;
576 : Assert(tabstat->trans == trans);
577 :
578 7238 : if (isCommit)
579 : {
580 5718 : if (trans->upper && trans->upper->nest_level == nestDepth - 1)
581 : {
582 3462 : if (trans->truncdropped)
583 : {
584 : /* propagate the truncate/drop status one level up */
585 24 : save_truncdrop_counters(trans->upper, false);
586 : /* replace upper xact stats with ours */
587 24 : trans->upper->tuples_inserted = trans->tuples_inserted;
588 24 : trans->upper->tuples_updated = trans->tuples_updated;
589 24 : trans->upper->tuples_deleted = trans->tuples_deleted;
590 : }
591 : else
592 : {
593 3438 : trans->upper->tuples_inserted += trans->tuples_inserted;
594 3438 : trans->upper->tuples_updated += trans->tuples_updated;
595 3438 : trans->upper->tuples_deleted += trans->tuples_deleted;
596 : }
597 3462 : tabstat->trans = trans->upper;
598 3462 : pfree(trans);
599 : }
600 : else
601 : {
602 : /*
603 : * When there isn't an immediate parent state, we can just
604 : * reuse the record instead of going through a palloc/pfree
605 : * pushup (this works since it's all in TopTransactionContext
606 : * anyway). We have to re-link it into the parent level,
607 : * though, and that might mean pushing a new entry into the
608 : * pgStatXactStack.
609 : */
610 : PgStat_SubXactStatus *upper_xact_state;
611 :
612 2256 : upper_xact_state = pgstat_get_xact_stack_level(nestDepth - 1);
613 2256 : trans->next = upper_xact_state->first;
614 2256 : upper_xact_state->first = trans;
615 2256 : trans->nest_level = nestDepth - 1;
616 : }
617 : }
618 : else
619 : {
620 : /*
621 : * On abort, update top-level tabstat counts, then forget the
622 : * subtransaction
623 : */
624 :
625 : /* first restore values obliterated by truncate/drop */
626 1520 : restore_truncdrop_counters(trans);
627 : /* count attempted actions regardless of commit/abort */
628 1520 : tabstat->counts.tuples_inserted += trans->tuples_inserted;
629 1520 : tabstat->counts.tuples_updated += trans->tuples_updated;
630 1520 : tabstat->counts.tuples_deleted += trans->tuples_deleted;
631 : /* inserted tuples are dead, deleted tuples are unaffected */
632 1520 : tabstat->counts.delta_dead_tuples +=
633 1520 : trans->tuples_inserted + trans->tuples_updated;
634 1520 : tabstat->trans = trans->upper;
635 1520 : pfree(trans);
636 : }
637 : }
638 6630 : }
639 :
640 : /*
641 : * Generate 2PC records for all the pending transaction-dependent relation
642 : * stats.
643 : */
644 : void
645 724 : AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
646 : {
647 : PgStat_TableXactStatus *trans;
648 :
649 1586 : for (trans = xact_state->first; trans != NULL; trans = trans->next)
650 : {
651 : PgStat_TableStatus *tabstat PG_USED_FOR_ASSERTS_ONLY;
652 : TwoPhasePgStatRecord record;
653 :
654 : Assert(trans->nest_level == 1);
655 : Assert(trans->upper == NULL);
656 862 : tabstat = trans->parent;
657 : Assert(tabstat->trans == trans);
658 :
659 862 : record.tuples_inserted = trans->tuples_inserted;
660 862 : record.tuples_updated = trans->tuples_updated;
661 862 : record.tuples_deleted = trans->tuples_deleted;
662 862 : record.inserted_pre_truncdrop = trans->inserted_pre_truncdrop;
663 862 : record.updated_pre_truncdrop = trans->updated_pre_truncdrop;
664 862 : record.deleted_pre_truncdrop = trans->deleted_pre_truncdrop;
665 862 : record.id = tabstat->id;
666 862 : record.shared = tabstat->shared;
667 862 : record.truncdropped = trans->truncdropped;
668 :
669 862 : RegisterTwoPhaseRecord(TWOPHASE_RM_PGSTAT_ID, 0,
670 : &record, sizeof(TwoPhasePgStatRecord));
671 : }
672 724 : }
673 :
674 : /*
675 : * All we need do here is unlink the transaction stats state from the
676 : * nontransactional state. The nontransactional action counts will be
677 : * reported to the stats system immediately, while the effects on live and
678 : * dead tuple counts are preserved in the 2PC state file.
679 : *
680 : * Note: AtEOXact_PgStat_Relations is not called during PREPARE.
681 : */
682 : void
683 724 : PostPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
684 : {
685 : PgStat_TableXactStatus *trans;
686 :
687 1586 : for (trans = xact_state->first; trans != NULL; trans = trans->next)
688 : {
689 : PgStat_TableStatus *tabstat;
690 :
691 862 : tabstat = trans->parent;
692 862 : tabstat->trans = NULL;
693 : }
694 724 : }
695 :
696 : /*
697 : * 2PC processing routine for COMMIT PREPARED case.
698 : *
699 : * Load the saved counts into our local pgstats state.
700 : */
701 : void
702 766 : pgstat_twophase_postcommit(TransactionId xid, uint16 info,
703 : void *recdata, uint32 len)
704 : {
705 766 : TwoPhasePgStatRecord *rec = (TwoPhasePgStatRecord *) recdata;
706 : PgStat_TableStatus *pgstat_info;
707 :
708 : /* Find or create a tabstat entry for the rel */
709 766 : pgstat_info = pgstat_prep_relation_pending(rec->id, rec->shared);
710 :
711 : /* Same math as in AtEOXact_PgStat, commit case */
712 766 : pgstat_info->counts.tuples_inserted += rec->tuples_inserted;
713 766 : pgstat_info->counts.tuples_updated += rec->tuples_updated;
714 766 : pgstat_info->counts.tuples_deleted += rec->tuples_deleted;
715 766 : pgstat_info->counts.truncdropped = rec->truncdropped;
716 766 : if (rec->truncdropped)
717 : {
718 : /* forget live/dead stats seen by backend thus far */
719 4 : pgstat_info->counts.delta_live_tuples = 0;
720 4 : pgstat_info->counts.delta_dead_tuples = 0;
721 : }
722 766 : pgstat_info->counts.delta_live_tuples +=
723 766 : rec->tuples_inserted - rec->tuples_deleted;
724 766 : pgstat_info->counts.delta_dead_tuples +=
725 766 : rec->tuples_updated + rec->tuples_deleted;
726 766 : pgstat_info->counts.changed_tuples +=
727 766 : rec->tuples_inserted + rec->tuples_updated +
728 766 : rec->tuples_deleted;
729 766 : }
730 :
731 : /*
732 : * 2PC processing routine for ROLLBACK PREPARED case.
733 : *
734 : * Load the saved counts into our local pgstats state, but treat them
735 : * as aborted.
736 : */
737 : void
738 120 : pgstat_twophase_postabort(TransactionId xid, uint16 info,
739 : void *recdata, uint32 len)
740 : {
741 120 : TwoPhasePgStatRecord *rec = (TwoPhasePgStatRecord *) recdata;
742 : PgStat_TableStatus *pgstat_info;
743 :
744 : /* Find or create a tabstat entry for the rel */
745 120 : pgstat_info = pgstat_prep_relation_pending(rec->id, rec->shared);
746 :
747 : /* Same math as in AtEOXact_PgStat, abort case */
748 120 : if (rec->truncdropped)
749 : {
750 8 : rec->tuples_inserted = rec->inserted_pre_truncdrop;
751 8 : rec->tuples_updated = rec->updated_pre_truncdrop;
752 8 : rec->tuples_deleted = rec->deleted_pre_truncdrop;
753 : }
754 120 : pgstat_info->counts.tuples_inserted += rec->tuples_inserted;
755 120 : pgstat_info->counts.tuples_updated += rec->tuples_updated;
756 120 : pgstat_info->counts.tuples_deleted += rec->tuples_deleted;
757 120 : pgstat_info->counts.delta_dead_tuples +=
758 120 : rec->tuples_inserted + rec->tuples_updated;
759 120 : }
760 :
761 : /*
762 : * Flush out pending stats for the entry
763 : *
764 : * If nowait is true, this function returns false if lock could not
765 : * immediately acquired, otherwise true is returned.
766 : *
767 : * Some of the stats are copied to the corresponding pending database stats
768 : * entry when successfully flushing.
769 : */
770 : bool
771 1314702 : pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
772 : {
773 : static const PgStat_TableCounts all_zeroes;
774 : Oid dboid;
775 : PgStat_TableStatus *lstats; /* pending stats entry */
776 : PgStatShared_Relation *shtabstats;
777 : PgStat_StatTabEntry *tabentry; /* table entry of shared stats */
778 : PgStat_StatDBEntry *dbentry; /* pending database entry */
779 :
780 1314702 : dboid = entry_ref->shared_entry->key.dboid;
781 1314702 : lstats = (PgStat_TableStatus *) entry_ref->pending;
782 1314702 : shtabstats = (PgStatShared_Relation *) entry_ref->shared_stats;
783 :
784 : /*
785 : * Ignore entries that didn't accumulate any actual counts, such as
786 : * indexes that were opened by the planner but not used.
787 : */
788 1314702 : if (memcmp(&lstats->counts, &all_zeroes,
789 : sizeof(PgStat_TableCounts)) == 0)
790 : {
791 18398 : return true;
792 : }
793 :
794 1296304 : if (!pgstat_lock_entry(entry_ref, nowait))
795 0 : return false;
796 :
797 : /* add the values to the shared entry. */
798 1296304 : tabentry = &shtabstats->stats;
799 :
800 1296304 : tabentry->numscans += lstats->counts.numscans;
801 1296304 : if (lstats->counts.numscans)
802 : {
803 789848 : TimestampTz t = GetCurrentTransactionStopTimestamp();
804 :
805 789848 : if (t > tabentry->lastscan)
806 777142 : tabentry->lastscan = t;
807 : }
808 1296304 : tabentry->tuples_returned += lstats->counts.tuples_returned;
809 1296304 : tabentry->tuples_fetched += lstats->counts.tuples_fetched;
810 1296304 : tabentry->tuples_inserted += lstats->counts.tuples_inserted;
811 1296304 : tabentry->tuples_updated += lstats->counts.tuples_updated;
812 1296304 : tabentry->tuples_deleted += lstats->counts.tuples_deleted;
813 1296304 : tabentry->tuples_hot_updated += lstats->counts.tuples_hot_updated;
814 1296304 : tabentry->tuples_newpage_updated += lstats->counts.tuples_newpage_updated;
815 :
816 : /*
817 : * If table was truncated/dropped, first reset the live/dead counters.
818 : */
819 1296304 : if (lstats->counts.truncdropped)
820 : {
821 394 : tabentry->live_tuples = 0;
822 394 : tabentry->dead_tuples = 0;
823 394 : tabentry->ins_since_vacuum = 0;
824 : }
825 :
826 1296304 : tabentry->live_tuples += lstats->counts.delta_live_tuples;
827 1296304 : tabentry->dead_tuples += lstats->counts.delta_dead_tuples;
828 1296304 : tabentry->mod_since_analyze += lstats->counts.changed_tuples;
829 1296304 : tabentry->ins_since_vacuum += lstats->counts.tuples_inserted;
830 1296304 : tabentry->blocks_fetched += lstats->counts.blocks_fetched;
831 1296304 : tabentry->blocks_hit += lstats->counts.blocks_hit;
832 :
833 : /* Clamp live_tuples in case of negative delta_live_tuples */
834 1296304 : tabentry->live_tuples = Max(tabentry->live_tuples, 0);
835 : /* Likewise for dead_tuples */
836 1296304 : tabentry->dead_tuples = Max(tabentry->dead_tuples, 0);
837 :
838 1296304 : pgstat_unlock_entry(entry_ref);
839 :
840 : /* The entry was successfully flushed, add the same to database stats */
841 1296304 : dbentry = pgstat_prep_database_pending(dboid);
842 1296304 : dbentry->tuples_returned += lstats->counts.tuples_returned;
843 1296304 : dbentry->tuples_fetched += lstats->counts.tuples_fetched;
844 1296304 : dbentry->tuples_inserted += lstats->counts.tuples_inserted;
845 1296304 : dbentry->tuples_updated += lstats->counts.tuples_updated;
846 1296304 : dbentry->tuples_deleted += lstats->counts.tuples_deleted;
847 1296304 : dbentry->blocks_fetched += lstats->counts.blocks_fetched;
848 1296304 : dbentry->blocks_hit += lstats->counts.blocks_hit;
849 :
850 1296304 : return true;
851 : }
852 :
853 : void
854 1370148 : pgstat_relation_delete_pending_cb(PgStat_EntryRef *entry_ref)
855 : {
856 1370148 : PgStat_TableStatus *pending = (PgStat_TableStatus *) entry_ref->pending;
857 :
858 1370148 : if (pending->relation)
859 1217186 : pgstat_unlink_relation(pending->relation);
860 1370148 : }
861 :
862 : /*
863 : * Find or create a PgStat_TableStatus entry for rel. New entry is created and
864 : * initialized if not exists.
865 : */
866 : static PgStat_TableStatus *
867 1483618 : pgstat_prep_relation_pending(Oid rel_id, bool isshared)
868 : {
869 : PgStat_EntryRef *entry_ref;
870 : PgStat_TableStatus *pending;
871 :
872 1483618 : entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_RELATION,
873 : isshared ? InvalidOid : MyDatabaseId,
874 : rel_id, NULL);
875 1483618 : pending = entry_ref->pending;
876 1483618 : pending->id = rel_id;
877 1483618 : pending->shared = isshared;
878 :
879 1483618 : return pending;
880 : }
881 :
882 : /*
883 : * add a new (sub)transaction state record
884 : */
885 : static void
886 1416770 : add_tabstat_xact_level(PgStat_TableStatus *pgstat_info, int nest_level)
887 : {
888 : PgStat_SubXactStatus *xact_state;
889 : PgStat_TableXactStatus *trans;
890 :
891 : /*
892 : * If this is the first rel to be modified at the current nest level, we
893 : * first have to push a transaction stack entry.
894 : */
895 1416770 : xact_state = pgstat_get_xact_stack_level(nest_level);
896 :
897 : /* Now make a per-table stack entry */
898 : trans = (PgStat_TableXactStatus *)
899 1416770 : MemoryContextAllocZero(TopTransactionContext,
900 : sizeof(PgStat_TableXactStatus));
901 1416770 : trans->nest_level = nest_level;
902 1416770 : trans->upper = pgstat_info->trans;
903 1416770 : trans->parent = pgstat_info;
904 1416770 : trans->next = xact_state->first;
905 1416770 : xact_state->first = trans;
906 1416770 : pgstat_info->trans = trans;
907 1416770 : }
908 :
909 : /*
910 : * Add a new (sub)transaction record if needed.
911 : */
912 : static void
913 26236650 : ensure_tabstat_xact_level(PgStat_TableStatus *pgstat_info)
914 : {
915 26236650 : int nest_level = GetCurrentTransactionNestLevel();
916 :
917 26236650 : if (pgstat_info->trans == NULL ||
918 24824458 : pgstat_info->trans->nest_level != nest_level)
919 1416770 : add_tabstat_xact_level(pgstat_info, nest_level);
920 26236650 : }
921 :
922 : /*
923 : * Whenever a table is truncated/dropped, we save its i/u/d counters so that
924 : * they can be cleared, and if the (sub)xact that executed the truncate/drop
925 : * later aborts, the counters can be restored to the saved (pre-truncate/drop)
926 : * values.
927 : *
928 : * Note that for truncate we do this on the first truncate in any particular
929 : * subxact level only.
930 : */
931 : static void
932 3358 : save_truncdrop_counters(PgStat_TableXactStatus *trans, bool is_drop)
933 : {
934 3358 : if (!trans->truncdropped || is_drop)
935 : {
936 3266 : trans->inserted_pre_truncdrop = trans->tuples_inserted;
937 3266 : trans->updated_pre_truncdrop = trans->tuples_updated;
938 3266 : trans->deleted_pre_truncdrop = trans->tuples_deleted;
939 3266 : trans->truncdropped = true;
940 : }
941 3358 : }
942 :
943 : /*
944 : * restore counters when a truncate aborts
945 : */
946 : static void
947 18144 : restore_truncdrop_counters(PgStat_TableXactStatus *trans)
948 : {
949 18144 : if (trans->truncdropped)
950 : {
951 202 : trans->tuples_inserted = trans->inserted_pre_truncdrop;
952 202 : trans->tuples_updated = trans->updated_pre_truncdrop;
953 202 : trans->tuples_deleted = trans->deleted_pre_truncdrop;
954 : }
955 18144 : }
|