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 488 : pgstat_copy_relation_stats(Relation dst, Relation src)
59 : {
60 : PgStat_StatTabEntry *srcstats;
61 : PgStatShared_Relation *dstshstats;
62 : PgStat_EntryRef *dst_ref;
63 :
64 488 : srcstats = pgstat_fetch_stat_tabentry_ext(src->rd_rel->relisshared,
65 : RelationGetRelid(src));
66 488 : if (!srcstats)
67 274 : return;
68 :
69 214 : dst_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION,
70 214 : dst->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
71 : RelationGetRelid(dst),
72 : false);
73 :
74 214 : dstshstats = (PgStatShared_Relation *) dst_ref->shared_stats;
75 214 : dstshstats->stats = *srcstats;
76 :
77 214 : 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 31236210 : pgstat_init_relation(Relation rel)
93 : {
94 31236210 : char relkind = rel->rd_rel->relkind;
95 :
96 : /*
97 : * We only count stats for relations with storage and partitioned tables
98 : */
99 31236210 : if (!RELKIND_HAS_STORAGE(relkind) && relkind != RELKIND_PARTITIONED_TABLE)
100 : {
101 126958 : rel->pgstat_enabled = false;
102 126958 : rel->pgstat_info = NULL;
103 126958 : return;
104 : }
105 :
106 31109252 : 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 31108908 : 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 1382564 : 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 2765128 : rel->pgstat_info = pgstat_prep_relation_pending(RelationGetRelid(rel),
139 1382564 : 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 1382564 : rel->pgstat_info->relation = rel;
146 1382564 : }
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 2170148 : pgstat_unlink_relation(Relation rel)
154 : {
155 : /* remove the link to stats info if any */
156 2170148 : if (rel->pgstat_info == NULL)
157 787584 : return;
158 :
159 : /* link sanity check */
160 : Assert(rel->pgstat_info->relation == rel);
161 1382564 : rel->pgstat_info->relation = NULL;
162 1382564 : rel->pgstat_info = NULL;
163 : }
164 :
165 : /*
166 : * Ensure that stats are dropped if transaction aborts.
167 : */
168 : void
169 118868 : pgstat_create_relation(Relation rel)
170 : {
171 118868 : pgstat_create_transactional(PGSTAT_KIND_RELATION,
172 118868 : rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
173 : RelationGetRelid(rel));
174 118868 : }
175 :
176 : /*
177 : * Ensure that stats are dropped if transaction commits.
178 : */
179 : void
180 65462 : pgstat_drop_relation(Relation rel)
181 : {
182 65462 : int nest_level = GetCurrentTransactionNestLevel();
183 : PgStat_TableStatus *pgstat_info;
184 :
185 65462 : pgstat_drop_transactional(PGSTAT_KIND_RELATION,
186 65462 : rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
187 : RelationGetRelid(rel));
188 :
189 65462 : if (!pgstat_should_count_relation(rel))
190 4312 : 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 61150 : pgstat_info = rel->pgstat_info;
197 61150 : 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 19100 : 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 19100 : Oid dboid = (shared ? InvalidOid : MyDatabaseId);
218 : TimestampTz ts;
219 :
220 19100 : if (!pgstat_track_counts)
221 0 : return;
222 :
223 : /* Store the data in the table's hash table entry. */
224 19100 : ts = GetCurrentTimestamp();
225 :
226 : /* block acquiring lock for the same reason as pgstat_report_autovac() */
227 19100 : entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION,
228 : dboid, tableoid, false);
229 :
230 19100 : shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats;
231 19100 : tabentry = &shtabentry->stats;
232 :
233 19100 : tabentry->live_tuples = livetuples;
234 19100 : 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 19100 : tabentry->ins_since_vacuum = 0;
247 :
248 19100 : if (AmAutoVacuumWorkerProcess())
249 : {
250 48 : tabentry->last_autovacuum_time = ts;
251 48 : tabentry->autovacuum_count++;
252 : }
253 : else
254 : {
255 19052 : tabentry->last_vacuum_time = ts;
256 19052 : tabentry->vacuum_count++;
257 : }
258 :
259 19100 : 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 19100 : 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 12262 : 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 12262 : Oid dboid = (rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId);
285 :
286 12262 : 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 12262 : if (pgstat_should_count_relation(rel) &&
302 12214 : rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
303 : {
304 : PgStat_TableXactStatus *trans;
305 :
306 11734 : 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 11588 : deadtuples -= rel->pgstat_info->counts.delta_dead_tuples;
313 : /* Since ANALYZE's counts are estimates, we could have underflowed */
314 11588 : livetuples = Max(livetuples, 0);
315 11588 : deadtuples = Max(deadtuples, 0);
316 : }
317 :
318 : /* block acquiring lock for the same reason as pgstat_report_autovac() */
319 12262 : 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 12262 : shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats;
326 12262 : tabentry = &shtabentry->stats;
327 :
328 12262 : tabentry->live_tuples = livetuples;
329 12262 : 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 12262 : if (resetcounter)
337 12218 : tabentry->mod_since_analyze = 0;
338 :
339 12262 : if (AmAutoVacuumWorkerProcess())
340 : {
341 108 : tabentry->last_autoanalyze_time = GetCurrentTimestamp();
342 108 : tabentry->autoanalyze_count++;
343 : }
344 : else
345 : {
346 12154 : tabentry->last_analyze_time = GetCurrentTimestamp();
347 12154 : tabentry->analyze_count++;
348 : }
349 :
350 12262 : pgstat_unlock_entry(entry_ref);
351 :
352 : /* see pgstat_report_vacuum() */
353 12262 : pgstat_flush_io(false);
354 : }
355 :
356 : /*
357 : * count a tuple insertion of n tuples
358 : */
359 : void
360 15749714 : pgstat_count_heap_insert(Relation rel, PgStat_Counter n)
361 : {
362 15749714 : if (pgstat_should_count_relation(rel))
363 : {
364 15457150 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
365 :
366 15457150 : ensure_tabstat_xact_level(pgstat_info);
367 15457150 : pgstat_info->trans->tuples_inserted += n;
368 : }
369 15749714 : }
370 :
371 : /*
372 : * count a tuple update
373 : */
374 : void
375 587086 : pgstat_count_heap_update(Relation rel, bool hot, bool newpage)
376 : {
377 : Assert(!(hot && newpage));
378 :
379 587086 : if (pgstat_should_count_relation(rel))
380 : {
381 587082 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
382 :
383 587082 : ensure_tabstat_xact_level(pgstat_info);
384 587082 : 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 587082 : if (hot)
391 263918 : pgstat_info->counts.tuples_hot_updated++;
392 323164 : else if (newpage)
393 301616 : pgstat_info->counts.tuples_newpage_updated++;
394 : }
395 587086 : }
396 :
397 : /*
398 : * count a tuple deletion
399 : */
400 : void
401 2778892 : pgstat_count_heap_delete(Relation rel)
402 : {
403 2778892 : if (pgstat_should_count_relation(rel))
404 : {
405 2778892 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
406 :
407 2778892 : ensure_tabstat_xact_level(pgstat_info);
408 2778892 : pgstat_info->trans->tuples_deleted++;
409 : }
410 2778892 : }
411 :
412 : /*
413 : * update tuple counters due to truncate
414 : */
415 : void
416 2804 : pgstat_count_truncate(Relation rel)
417 : {
418 2804 : if (pgstat_should_count_relation(rel))
419 : {
420 2804 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
421 :
422 2804 : ensure_tabstat_xact_level(pgstat_info);
423 2804 : save_truncdrop_counters(pgstat_info->trans, false);
424 2804 : pgstat_info->trans->tuples_inserted = 0;
425 2804 : pgstat_info->trans->tuples_updated = 0;
426 2804 : pgstat_info->trans->tuples_deleted = 0;
427 : }
428 2804 : }
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 32724 : pgstat_update_heap_dead_tuples(Relation rel, int delta)
440 : {
441 32724 : if (pgstat_should_count_relation(rel))
442 : {
443 32724 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
444 :
445 32724 : pgstat_info->counts.delta_dead_tuples -= delta;
446 : }
447 32724 : }
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 274616 : pgstat_fetch_stat_tabentry(Oid relid)
457 : {
458 274616 : 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 279274 : pgstat_fetch_stat_tabentry_ext(bool shared, Oid reloid)
467 : {
468 279274 : Oid dboid = (shared ? InvalidOid : MyDatabaseId);
469 :
470 279274 : return (PgStat_StatTabEntry *)
471 279274 : 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 223642 : AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit)
538 : {
539 : PgStat_TableXactStatus *trans;
540 :
541 837640 : 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 613998 : tabstat = trans->parent;
548 : Assert(tabstat->trans == trans);
549 : /* restore pre-truncate/drop stats (if any) in case of aborted xact */
550 613998 : if (!isCommit)
551 19516 : restore_truncdrop_counters(trans);
552 : /* count attempted actions regardless of commit/abort */
553 613998 : tabstat->counts.tuples_inserted += trans->tuples_inserted;
554 613998 : tabstat->counts.tuples_updated += trans->tuples_updated;
555 613998 : tabstat->counts.tuples_deleted += trans->tuples_deleted;
556 613998 : if (isCommit)
557 : {
558 594482 : tabstat->counts.truncdropped = trans->truncdropped;
559 594482 : if (trans->truncdropped)
560 : {
561 : /* forget live/dead stats seen by backend thus far */
562 3332 : tabstat->counts.delta_live_tuples = 0;
563 3332 : tabstat->counts.delta_dead_tuples = 0;
564 : }
565 : /* insert adds a live tuple, delete removes one */
566 594482 : tabstat->counts.delta_live_tuples +=
567 594482 : trans->tuples_inserted - trans->tuples_deleted;
568 : /* update and delete each create a dead tuple */
569 594482 : tabstat->counts.delta_dead_tuples +=
570 594482 : trans->tuples_updated + trans->tuples_deleted;
571 : /* insert, update, delete each count as one change event */
572 594482 : tabstat->counts.changed_tuples +=
573 594482 : trans->tuples_inserted + trans->tuples_updated +
574 594482 : trans->tuples_deleted;
575 : }
576 : else
577 : {
578 : /* inserted tuples are dead, deleted tuples are unaffected */
579 19516 : tabstat->counts.delta_dead_tuples +=
580 19516 : trans->tuples_inserted + trans->tuples_updated;
581 : /* an aborted xact generates no changed_tuple events */
582 : }
583 613998 : tabstat->trans = NULL;
584 : }
585 223642 : }
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 6710 : AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth)
596 : {
597 : PgStat_TableXactStatus *trans;
598 : PgStat_TableXactStatus *next_trans;
599 :
600 14032 : for (trans = xact_state->first; trans != NULL; trans = next_trans)
601 : {
602 : PgStat_TableStatus *tabstat;
603 :
604 7322 : next_trans = trans->next;
605 : Assert(trans->nest_level == nestDepth);
606 7322 : tabstat = trans->parent;
607 : Assert(tabstat->trans == trans);
608 :
609 7322 : if (isCommit)
610 : {
611 5806 : if (trans->upper && trans->upper->nest_level == nestDepth - 1)
612 : {
613 3460 : 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 3436 : trans->upper->tuples_inserted += trans->tuples_inserted;
625 3436 : trans->upper->tuples_updated += trans->tuples_updated;
626 3436 : trans->upper->tuples_deleted += trans->tuples_deleted;
627 : }
628 3460 : tabstat->trans = trans->upper;
629 3460 : 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 2346 : upper_xact_state = pgstat_get_xact_stack_level(nestDepth - 1);
644 2346 : trans->next = upper_xact_state->first;
645 2346 : upper_xact_state->first = trans;
646 2346 : 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 6710 : }
670 :
671 : /*
672 : * Generate 2PC records for all the pending transaction-dependent relation
673 : * stats.
674 : */
675 : void
676 746 : AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
677 : {
678 : PgStat_TableXactStatus *trans;
679 :
680 1660 : 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 914 : tabstat = trans->parent;
688 : Assert(tabstat->trans == trans);
689 :
690 914 : record.tuples_inserted = trans->tuples_inserted;
691 914 : record.tuples_updated = trans->tuples_updated;
692 914 : record.tuples_deleted = trans->tuples_deleted;
693 914 : record.inserted_pre_truncdrop = trans->inserted_pre_truncdrop;
694 914 : record.updated_pre_truncdrop = trans->updated_pre_truncdrop;
695 914 : record.deleted_pre_truncdrop = trans->deleted_pre_truncdrop;
696 914 : record.id = tabstat->id;
697 914 : record.shared = tabstat->shared;
698 914 : record.truncdropped = trans->truncdropped;
699 :
700 914 : RegisterTwoPhaseRecord(TWOPHASE_RM_PGSTAT_ID, 0,
701 : &record, sizeof(TwoPhasePgStatRecord));
702 : }
703 746 : }
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 746 : PostPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
715 : {
716 : PgStat_TableXactStatus *trans;
717 :
718 1660 : for (trans = xact_state->first; trans != NULL; trans = trans->next)
719 : {
720 : PgStat_TableStatus *tabstat;
721 :
722 914 : tabstat = trans->parent;
723 914 : tabstat->trans = NULL;
724 : }
725 746 : }
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 784 : pgstat_twophase_postcommit(TransactionId xid, uint16 info,
734 : void *recdata, uint32 len)
735 : {
736 784 : TwoPhasePgStatRecord *rec = (TwoPhasePgStatRecord *) recdata;
737 : PgStat_TableStatus *pgstat_info;
738 :
739 : /* Find or create a tabstat entry for the rel */
740 784 : pgstat_info = pgstat_prep_relation_pending(rec->id, rec->shared);
741 :
742 : /* Same math as in AtEOXact_PgStat, commit case */
743 784 : pgstat_info->counts.tuples_inserted += rec->tuples_inserted;
744 784 : pgstat_info->counts.tuples_updated += rec->tuples_updated;
745 784 : pgstat_info->counts.tuples_deleted += rec->tuples_deleted;
746 784 : pgstat_info->counts.truncdropped = rec->truncdropped;
747 784 : 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 784 : pgstat_info->counts.delta_live_tuples +=
754 784 : rec->tuples_inserted - rec->tuples_deleted;
755 784 : pgstat_info->counts.delta_dead_tuples +=
756 784 : rec->tuples_updated + rec->tuples_deleted;
757 784 : pgstat_info->counts.changed_tuples +=
758 784 : rec->tuples_inserted + rec->tuples_updated +
759 784 : rec->tuples_deleted;
760 784 : }
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 1235628 : 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 1235628 : dboid = entry_ref->shared_entry->key.dboid;
812 1235628 : lstats = (PgStat_TableStatus *) entry_ref->pending;
813 1235628 : 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 1235628 : if (memcmp(&lstats->counts, &all_zeroes,
820 : sizeof(PgStat_TableCounts)) == 0)
821 : {
822 4114 : return true;
823 : }
824 :
825 1231514 : if (!pgstat_lock_entry(entry_ref, nowait))
826 0 : return false;
827 :
828 : /* add the values to the shared entry. */
829 1231514 : tabentry = &shtabstats->stats;
830 :
831 1231514 : tabentry->numscans += lstats->counts.numscans;
832 1231514 : if (lstats->counts.numscans)
833 : {
834 756866 : TimestampTz t = GetCurrentTransactionStopTimestamp();
835 :
836 756866 : if (t > tabentry->lastscan)
837 743660 : tabentry->lastscan = t;
838 : }
839 1231514 : tabentry->tuples_returned += lstats->counts.tuples_returned;
840 1231514 : tabentry->tuples_fetched += lstats->counts.tuples_fetched;
841 1231514 : tabentry->tuples_inserted += lstats->counts.tuples_inserted;
842 1231514 : tabentry->tuples_updated += lstats->counts.tuples_updated;
843 1231514 : tabentry->tuples_deleted += lstats->counts.tuples_deleted;
844 1231514 : tabentry->tuples_hot_updated += lstats->counts.tuples_hot_updated;
845 1231514 : 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 1231514 : if (lstats->counts.truncdropped)
851 : {
852 436 : tabentry->live_tuples = 0;
853 436 : tabentry->dead_tuples = 0;
854 436 : tabentry->ins_since_vacuum = 0;
855 : }
856 :
857 1231514 : tabentry->live_tuples += lstats->counts.delta_live_tuples;
858 1231514 : tabentry->dead_tuples += lstats->counts.delta_dead_tuples;
859 1231514 : tabentry->mod_since_analyze += lstats->counts.changed_tuples;
860 1231514 : tabentry->ins_since_vacuum += lstats->counts.tuples_inserted;
861 1231514 : tabentry->blocks_fetched += lstats->counts.blocks_fetched;
862 1231514 : tabentry->blocks_hit += lstats->counts.blocks_hit;
863 :
864 : /* Clamp live_tuples in case of negative delta_live_tuples */
865 1231514 : tabentry->live_tuples = Max(tabentry->live_tuples, 0);
866 : /* Likewise for dead_tuples */
867 1231514 : tabentry->dead_tuples = Max(tabentry->dead_tuples, 0);
868 :
869 1231514 : pgstat_unlock_entry(entry_ref);
870 :
871 : /* The entry was successfully flushed, add the same to database stats */
872 1231514 : dbentry = pgstat_prep_database_pending(dboid);
873 1231514 : dbentry->tuples_returned += lstats->counts.tuples_returned;
874 1231514 : dbentry->tuples_fetched += lstats->counts.tuples_fetched;
875 1231514 : dbentry->tuples_inserted += lstats->counts.tuples_inserted;
876 1231514 : dbentry->tuples_updated += lstats->counts.tuples_updated;
877 1231514 : dbentry->tuples_deleted += lstats->counts.tuples_deleted;
878 1231514 : dbentry->blocks_fetched += lstats->counts.blocks_fetched;
879 1231514 : dbentry->blocks_hit += lstats->counts.blocks_hit;
880 :
881 1231514 : return true;
882 : }
883 :
884 : void
885 1297616 : pgstat_relation_delete_pending_cb(PgStat_EntryRef *entry_ref)
886 : {
887 1297616 : PgStat_TableStatus *pending = (PgStat_TableStatus *) entry_ref->pending;
888 :
889 1297616 : if (pending->relation)
890 1173220 : pgstat_unlink_relation(pending->relation);
891 1297616 : }
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 1383472 : pgstat_prep_relation_pending(Oid rel_id, bool isshared)
899 : {
900 : PgStat_EntryRef *entry_ref;
901 : PgStat_TableStatus *pending;
902 :
903 1383472 : entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_RELATION,
904 : isshared ? InvalidOid : MyDatabaseId,
905 : rel_id, NULL);
906 1383472 : pending = entry_ref->pending;
907 1383472 : pending->id = rel_id;
908 1383472 : pending->shared = isshared;
909 :
910 1383472 : return pending;
911 : }
912 :
913 : /*
914 : * add a new (sub)transaction state record
915 : */
916 : static void
917 619888 : 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 619888 : xact_state = pgstat_get_xact_stack_level(nest_level);
927 :
928 : /* Now make a per-table stack entry */
929 : trans = (PgStat_TableXactStatus *)
930 619888 : MemoryContextAllocZero(TopTransactionContext,
931 : sizeof(PgStat_TableXactStatus));
932 619888 : trans->nest_level = nest_level;
933 619888 : trans->upper = pgstat_info->trans;
934 619888 : trans->parent = pgstat_info;
935 619888 : trans->next = xact_state->first;
936 619888 : xact_state->first = trans;
937 619888 : pgstat_info->trans = trans;
938 619888 : }
939 :
940 : /*
941 : * Add a new (sub)transaction record if needed.
942 : */
943 : static void
944 18825928 : ensure_tabstat_xact_level(PgStat_TableStatus *pgstat_info)
945 : {
946 18825928 : int nest_level = GetCurrentTransactionNestLevel();
947 :
948 18825928 : if (pgstat_info->trans == NULL ||
949 18210610 : pgstat_info->trans->nest_level != nest_level)
950 619888 : add_tabstat_xact_level(pgstat_info, nest_level);
951 18825928 : }
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 3694 : save_truncdrop_counters(PgStat_TableXactStatus *trans, bool is_drop)
964 : {
965 3694 : if (!trans->truncdropped || is_drop)
966 : {
967 3602 : trans->inserted_pre_truncdrop = trans->tuples_inserted;
968 3602 : trans->updated_pre_truncdrop = trans->tuples_updated;
969 3602 : trans->deleted_pre_truncdrop = trans->tuples_deleted;
970 3602 : trans->truncdropped = true;
971 : }
972 3694 : }
973 :
974 : /*
975 : * restore counters when a truncate aborts
976 : */
977 : static void
978 21032 : restore_truncdrop_counters(PgStat_TableXactStatus *trans)
979 : {
980 21032 : 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 21032 : }
|