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