Line data Source code
1 : /* -------------------------------------------------------------------------
2 : *
3 : * pgstat_relation.c
4 : * Implementation of relation statistics.
5 : *
6 : * This file contains the implementation of relation statistics. 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-2025, 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 514 : pgstat_copy_relation_stats(Relation dst, Relation src)
58 : {
59 : PgStat_StatTabEntry *srcstats;
60 : PgStatShared_Relation *dstshstats;
61 : PgStat_EntryRef *dst_ref;
62 :
63 514 : srcstats = pgstat_fetch_stat_tabentry_ext(src->rd_rel->relisshared,
64 : RelationGetRelid(src));
65 514 : if (!srcstats)
66 294 : return;
67 :
68 220 : dst_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION,
69 220 : dst->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
70 220 : RelationGetRelid(dst),
71 : false);
72 :
73 220 : dstshstats = (PgStatShared_Relation *) dst_ref->shared_stats;
74 220 : dstshstats->stats = *srcstats;
75 :
76 220 : 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 44927710 : pgstat_init_relation(Relation rel)
92 : {
93 44927710 : char relkind = rel->rd_rel->relkind;
94 :
95 : /*
96 : * We only count stats for relations with storage and partitioned tables
97 : */
98 44927710 : if (!RELKIND_HAS_STORAGE(relkind) && relkind != RELKIND_PARTITIONED_TABLE)
99 : {
100 157124 : rel->pgstat_enabled = false;
101 157124 : rel->pgstat_info = NULL;
102 157124 : return;
103 : }
104 :
105 44770586 : if (!pgstat_track_counts)
106 : {
107 394 : if (rel->pgstat_info)
108 24 : pgstat_unlink_relation(rel);
109 :
110 : /* We're not counting at all */
111 394 : rel->pgstat_enabled = false;
112 394 : rel->pgstat_info = NULL;
113 394 : return;
114 : }
115 :
116 44770192 : 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 2119334 : 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 4238668 : rel->pgstat_info = pgstat_prep_relation_pending(RelationGetRelid(rel),
138 2119334 : 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 2119334 : rel->pgstat_info->relation = rel;
145 2119334 : }
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 3176598 : pgstat_unlink_relation(Relation rel)
153 : {
154 : /* remove the link to stats info if any */
155 3176598 : if (rel->pgstat_info == NULL)
156 1057264 : return;
157 :
158 : /* link sanity check */
159 : Assert(rel->pgstat_info->relation == rel);
160 2119334 : rel->pgstat_info->relation = NULL;
161 2119334 : rel->pgstat_info = NULL;
162 : }
163 :
164 : /*
165 : * Ensure that stats are dropped if transaction aborts.
166 : */
167 : void
168 141880 : pgstat_create_relation(Relation rel)
169 : {
170 141880 : pgstat_create_transactional(PGSTAT_KIND_RELATION,
171 141880 : rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
172 141880 : RelationGetRelid(rel));
173 141880 : }
174 :
175 : /*
176 : * Ensure that stats are dropped if transaction commits.
177 : */
178 : void
179 77000 : pgstat_drop_relation(Relation rel)
180 : {
181 77000 : int nest_level = GetCurrentTransactionNestLevel();
182 : PgStat_TableStatus *pgstat_info;
183 :
184 77000 : pgstat_drop_transactional(PGSTAT_KIND_RELATION,
185 77000 : rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
186 77000 : RelationGetRelid(rel));
187 :
188 77000 : if (!pgstat_should_count_relation(rel))
189 8464 : 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 68536 : pgstat_info = rel->pgstat_info;
196 68536 : if (pgstat_info->trans &&
197 1372 : pgstat_info->trans->nest_level == nest_level)
198 : {
199 1366 : save_truncdrop_counters(pgstat_info->trans, true);
200 1366 : pgstat_info->trans->tuples_inserted = 0;
201 1366 : pgstat_info->trans->tuples_updated = 0;
202 1366 : 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 240540 : pgstat_report_vacuum(Relation rel, PgStat_Counter livetuples,
211 : PgStat_Counter deadtuples, TimestampTz starttime)
212 : {
213 : PgStat_EntryRef *entry_ref;
214 : PgStatShared_Relation *shtabentry;
215 : PgStat_StatTabEntry *tabentry;
216 240540 : Oid dboid = (rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId);
217 : TimestampTz ts;
218 : PgStat_Counter elapsedtime;
219 :
220 240540 : if (!pgstat_track_counts)
221 0 : return;
222 :
223 : /* Store the data in the table's hash table entry. */
224 240540 : ts = GetCurrentTimestamp();
225 240540 : elapsedtime = TimestampDifferenceMilliseconds(starttime, ts);
226 :
227 : /* block acquiring lock for the same reason as pgstat_report_autovac() */
228 240540 : entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, dboid,
229 240540 : RelationGetRelid(rel), false);
230 :
231 240540 : shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats;
232 240540 : tabentry = &shtabentry->stats;
233 :
234 240540 : tabentry->live_tuples = livetuples;
235 240540 : 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 240540 : tabentry->ins_since_vacuum = 0;
248 :
249 240540 : if (AmAutoVacuumWorkerProcess())
250 : {
251 212604 : tabentry->last_autovacuum_time = ts;
252 212604 : tabentry->autovacuum_count++;
253 212604 : tabentry->total_autovacuum_time += elapsedtime;
254 : }
255 : else
256 : {
257 27936 : tabentry->last_vacuum_time = ts;
258 27936 : tabentry->vacuum_count++;
259 27936 : tabentry->total_vacuum_time += elapsedtime;
260 : }
261 :
262 240540 : pgstat_unlock_entry(entry_ref);
263 :
264 : /*
265 : * Flush IO statistics now. pgstat_report_stat() will flush IO stats,
266 : * however this will not be called until after an entire autovacuum cycle
267 : * is done -- which will likely vacuum many relations -- or until the
268 : * VACUUM command has processed all tables and committed.
269 : */
270 240540 : pgstat_flush_io(false);
271 240540 : (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO);
272 : }
273 :
274 : /*
275 : * Report that the table was just analyzed and flush IO statistics.
276 : *
277 : * Caller must provide new live- and dead-tuples estimates, as well as a
278 : * flag indicating whether to reset the mod_since_analyze counter.
279 : */
280 : void
281 16578 : pgstat_report_analyze(Relation rel,
282 : PgStat_Counter livetuples, PgStat_Counter deadtuples,
283 : bool resetcounter, TimestampTz starttime)
284 : {
285 : PgStat_EntryRef *entry_ref;
286 : PgStatShared_Relation *shtabentry;
287 : PgStat_StatTabEntry *tabentry;
288 16578 : Oid dboid = (rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId);
289 : TimestampTz ts;
290 : PgStat_Counter elapsedtime;
291 :
292 16578 : if (!pgstat_track_counts)
293 0 : return;
294 :
295 : /*
296 : * Unlike VACUUM, ANALYZE might be running inside a transaction that has
297 : * already inserted and/or deleted rows in the target table. ANALYZE will
298 : * have counted such rows as live or dead respectively. Because we will
299 : * report our counts of such rows at transaction end, we should subtract
300 : * off these counts from the update we're making now, else they'll be
301 : * double-counted after commit. (This approach also ensures that the
302 : * shared stats entry ends up with the right numbers if we abort instead
303 : * of committing.)
304 : *
305 : * Waste no time on partitioned tables, though.
306 : */
307 16578 : if (pgstat_should_count_relation(rel) &&
308 16514 : rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
309 : {
310 : PgStat_TableXactStatus *trans;
311 :
312 15962 : for (trans = rel->pgstat_info->trans; trans; trans = trans->upper)
313 : {
314 198 : livetuples -= trans->tuples_inserted - trans->tuples_deleted;
315 198 : deadtuples -= trans->tuples_updated + trans->tuples_deleted;
316 : }
317 : /* count stuff inserted by already-aborted subxacts, too */
318 15764 : deadtuples -= rel->pgstat_info->counts.delta_dead_tuples;
319 : /* Since ANALYZE's counts are estimates, we could have underflowed */
320 15764 : livetuples = Max(livetuples, 0);
321 15764 : deadtuples = Max(deadtuples, 0);
322 : }
323 :
324 : /* Store the data in the table's hash table entry. */
325 16578 : ts = GetCurrentTimestamp();
326 16578 : elapsedtime = TimestampDifferenceMilliseconds(starttime, ts);
327 :
328 : /* block acquiring lock for the same reason as pgstat_report_autovac() */
329 16578 : entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, dboid,
330 16578 : RelationGetRelid(rel),
331 : false);
332 : /* can't get dropped while accessed */
333 : Assert(entry_ref != NULL && entry_ref->shared_stats != NULL);
334 :
335 16578 : shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats;
336 16578 : tabentry = &shtabentry->stats;
337 :
338 16578 : tabentry->live_tuples = livetuples;
339 16578 : tabentry->dead_tuples = deadtuples;
340 :
341 : /*
342 : * If commanded, reset mod_since_analyze to zero. This forgets any
343 : * changes that were committed while the ANALYZE was in progress, but we
344 : * have no good way to estimate how many of those there were.
345 : */
346 16578 : if (resetcounter)
347 16528 : tabentry->mod_since_analyze = 0;
348 :
349 16578 : if (AmAutoVacuumWorkerProcess())
350 : {
351 438 : tabentry->last_autoanalyze_time = ts;
352 438 : tabentry->autoanalyze_count++;
353 438 : tabentry->total_autoanalyze_time += elapsedtime;
354 : }
355 : else
356 : {
357 16140 : tabentry->last_analyze_time = ts;
358 16140 : tabentry->analyze_count++;
359 16140 : tabentry->total_analyze_time += elapsedtime;
360 : }
361 :
362 16578 : pgstat_unlock_entry(entry_ref);
363 :
364 : /* see pgstat_report_vacuum() */
365 16578 : pgstat_flush_io(false);
366 16578 : (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO);
367 : }
368 :
369 : /*
370 : * count a tuple insertion of n tuples
371 : */
372 : void
373 17509876 : pgstat_count_heap_insert(Relation rel, PgStat_Counter n)
374 : {
375 17509876 : if (pgstat_should_count_relation(rel))
376 : {
377 17126658 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
378 :
379 17126658 : ensure_tabstat_xact_level(pgstat_info);
380 17126658 : pgstat_info->trans->tuples_inserted += n;
381 : }
382 17509876 : }
383 :
384 : /*
385 : * count a tuple update
386 : */
387 : void
388 626606 : pgstat_count_heap_update(Relation rel, bool hot, bool newpage)
389 : {
390 : Assert(!(hot && newpage));
391 :
392 626606 : if (pgstat_should_count_relation(rel))
393 : {
394 626602 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
395 :
396 626602 : ensure_tabstat_xact_level(pgstat_info);
397 626602 : pgstat_info->trans->tuples_updated++;
398 :
399 : /*
400 : * tuples_hot_updated and tuples_newpage_updated counters are
401 : * nontransactional, so just advance them
402 : */
403 626602 : if (hot)
404 298272 : pgstat_info->counts.tuples_hot_updated++;
405 328330 : else if (newpage)
406 303822 : pgstat_info->counts.tuples_newpage_updated++;
407 : }
408 626606 : }
409 :
410 : /*
411 : * count a tuple deletion
412 : */
413 : void
414 3049758 : pgstat_count_heap_delete(Relation rel)
415 : {
416 3049758 : if (pgstat_should_count_relation(rel))
417 : {
418 3049758 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
419 :
420 3049758 : ensure_tabstat_xact_level(pgstat_info);
421 3049758 : pgstat_info->trans->tuples_deleted++;
422 : }
423 3049758 : }
424 :
425 : /*
426 : * update tuple counters due to truncate
427 : */
428 : void
429 3562 : pgstat_count_truncate(Relation rel)
430 : {
431 3562 : if (pgstat_should_count_relation(rel))
432 : {
433 3562 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
434 :
435 3562 : ensure_tabstat_xact_level(pgstat_info);
436 3562 : save_truncdrop_counters(pgstat_info->trans, false);
437 3562 : pgstat_info->trans->tuples_inserted = 0;
438 3562 : pgstat_info->trans->tuples_updated = 0;
439 3562 : pgstat_info->trans->tuples_deleted = 0;
440 : }
441 3562 : }
442 :
443 : /*
444 : * update dead-tuples count
445 : *
446 : * The semantics of this are that we are reporting the nontransactional
447 : * recovery of "delta" dead tuples; so delta_dead_tuples decreases
448 : * rather than increasing, and the change goes straight into the per-table
449 : * counter, not into transactional state.
450 : */
451 : void
452 39074 : pgstat_update_heap_dead_tuples(Relation rel, int delta)
453 : {
454 39074 : if (pgstat_should_count_relation(rel))
455 : {
456 39074 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
457 :
458 39074 : pgstat_info->counts.delta_dead_tuples -= delta;
459 : }
460 39074 : }
461 :
462 : /*
463 : * Support function for the SQL-callable pgstat* functions. Returns
464 : * the collected statistics for one table or NULL. NULL doesn't mean
465 : * that the table doesn't exist, just that there are no statistics, so the
466 : * caller is better off to report ZERO instead.
467 : */
468 : PgStat_StatTabEntry *
469 9766 : pgstat_fetch_stat_tabentry(Oid relid)
470 : {
471 9766 : return pgstat_fetch_stat_tabentry_ext(IsSharedRelation(relid), relid);
472 : }
473 :
474 : /*
475 : * More efficient version of pgstat_fetch_stat_tabentry(), allowing to specify
476 : * whether the to-be-accessed table is a shared relation or not.
477 : */
478 : PgStat_StatTabEntry *
479 681648 : pgstat_fetch_stat_tabentry_ext(bool shared, Oid reloid)
480 : {
481 681648 : Oid dboid = (shared ? InvalidOid : MyDatabaseId);
482 :
483 681648 : return (PgStat_StatTabEntry *)
484 681648 : pgstat_fetch_entry(PGSTAT_KIND_RELATION, dboid, reloid);
485 : }
486 :
487 : /*
488 : * find any existing PgStat_TableStatus entry for rel
489 : *
490 : * Find any existing PgStat_TableStatus entry for rel_id in the current
491 : * database. If not found, try finding from shared tables.
492 : *
493 : * If an entry is found, copy it and increment the copy's counters with their
494 : * subtransaction counterparts, then return the copy. The caller may need to
495 : * pfree() the copy.
496 : *
497 : * If no entry found, return NULL, don't create a new one.
498 : */
499 : PgStat_TableStatus *
500 48 : find_tabstat_entry(Oid rel_id)
501 : {
502 : PgStat_EntryRef *entry_ref;
503 : PgStat_TableXactStatus *trans;
504 48 : PgStat_TableStatus *tabentry = NULL;
505 48 : PgStat_TableStatus *tablestatus = NULL;
506 :
507 48 : entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_RELATION, MyDatabaseId, rel_id);
508 48 : if (!entry_ref)
509 : {
510 12 : entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_RELATION, InvalidOid, rel_id);
511 12 : if (!entry_ref)
512 12 : return tablestatus;
513 : }
514 :
515 36 : tabentry = (PgStat_TableStatus *) entry_ref->pending;
516 36 : tablestatus = palloc_object(PgStat_TableStatus);
517 36 : *tablestatus = *tabentry;
518 :
519 : /*
520 : * Reset tablestatus->trans in the copy of PgStat_TableStatus as it may
521 : * point to a shared memory area. Its data is saved below, so removing it
522 : * does not matter.
523 : */
524 36 : tablestatus->trans = NULL;
525 :
526 : /*
527 : * Live subtransaction counts are not included yet. This is not a hot
528 : * code path so reconcile tuples_inserted, tuples_updated and
529 : * tuples_deleted even if the caller may not be interested in this data.
530 : */
531 84 : for (trans = tabentry->trans; trans != NULL; trans = trans->upper)
532 : {
533 48 : tablestatus->counts.tuples_inserted += trans->tuples_inserted;
534 48 : tablestatus->counts.tuples_updated += trans->tuples_updated;
535 48 : tablestatus->counts.tuples_deleted += trans->tuples_deleted;
536 : }
537 :
538 36 : return tablestatus;
539 : }
540 :
541 : /*
542 : * Perform relation stats specific end-of-transaction work. Helper for
543 : * AtEOXact_PgStat.
544 : *
545 : * Transfer transactional insert/update counts into the base tabstat entries.
546 : * We don't bother to free any of the transactional state, since it's all in
547 : * TopTransactionContext and will go away anyway.
548 : */
549 : void
550 259478 : AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit)
551 : {
552 : PgStat_TableXactStatus *trans;
553 :
554 971364 : for (trans = xact_state->first; trans != NULL; trans = trans->next)
555 : {
556 : PgStat_TableStatus *tabstat;
557 :
558 : Assert(trans->nest_level == 1);
559 : Assert(trans->upper == NULL);
560 711886 : tabstat = trans->parent;
561 : Assert(tabstat->trans == trans);
562 : /* restore pre-truncate/drop stats (if any) in case of aborted xact */
563 711886 : if (!isCommit)
564 23540 : restore_truncdrop_counters(trans);
565 : /* count attempted actions regardless of commit/abort */
566 711886 : tabstat->counts.tuples_inserted += trans->tuples_inserted;
567 711886 : tabstat->counts.tuples_updated += trans->tuples_updated;
568 711886 : tabstat->counts.tuples_deleted += trans->tuples_deleted;
569 711886 : if (isCommit)
570 : {
571 688346 : tabstat->counts.truncdropped = trans->truncdropped;
572 688346 : if (trans->truncdropped)
573 : {
574 : /* forget live/dead stats seen by backend thus far */
575 4434 : tabstat->counts.delta_live_tuples = 0;
576 4434 : tabstat->counts.delta_dead_tuples = 0;
577 : }
578 : /* insert adds a live tuple, delete removes one */
579 688346 : tabstat->counts.delta_live_tuples +=
580 688346 : trans->tuples_inserted - trans->tuples_deleted;
581 : /* update and delete each create a dead tuple */
582 688346 : tabstat->counts.delta_dead_tuples +=
583 688346 : trans->tuples_updated + trans->tuples_deleted;
584 : /* insert, update, delete each count as one change event */
585 688346 : tabstat->counts.changed_tuples +=
586 688346 : trans->tuples_inserted + trans->tuples_updated +
587 688346 : trans->tuples_deleted;
588 : }
589 : else
590 : {
591 : /* inserted tuples are dead, deleted tuples are unaffected */
592 23540 : tabstat->counts.delta_dead_tuples +=
593 23540 : trans->tuples_inserted + trans->tuples_updated;
594 : /* an aborted xact generates no changed_tuple events */
595 : }
596 711886 : tabstat->trans = NULL;
597 : }
598 259478 : }
599 :
600 : /*
601 : * Perform relation stats specific end-of-sub-transaction work. Helper for
602 : * AtEOSubXact_PgStat.
603 : *
604 : * Transfer transactional insert/update counts into the next higher
605 : * subtransaction state.
606 : */
607 : void
608 8442 : AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth)
609 : {
610 : PgStat_TableXactStatus *trans;
611 : PgStat_TableXactStatus *next_trans;
612 :
613 17662 : for (trans = xact_state->first; trans != NULL; trans = next_trans)
614 : {
615 : PgStat_TableStatus *tabstat;
616 :
617 9220 : next_trans = trans->next;
618 : Assert(trans->nest_level == nestDepth);
619 9220 : tabstat = trans->parent;
620 : Assert(tabstat->trans == trans);
621 :
622 9220 : if (isCommit)
623 : {
624 7560 : if (trans->upper && trans->upper->nest_level == nestDepth - 1)
625 : {
626 5284 : if (trans->truncdropped)
627 : {
628 : /* propagate the truncate/drop status one level up */
629 24 : save_truncdrop_counters(trans->upper, false);
630 : /* replace upper xact stats with ours */
631 24 : trans->upper->tuples_inserted = trans->tuples_inserted;
632 24 : trans->upper->tuples_updated = trans->tuples_updated;
633 24 : trans->upper->tuples_deleted = trans->tuples_deleted;
634 : }
635 : else
636 : {
637 5260 : trans->upper->tuples_inserted += trans->tuples_inserted;
638 5260 : trans->upper->tuples_updated += trans->tuples_updated;
639 5260 : trans->upper->tuples_deleted += trans->tuples_deleted;
640 : }
641 5284 : tabstat->trans = trans->upper;
642 5284 : pfree(trans);
643 : }
644 : else
645 : {
646 : /*
647 : * When there isn't an immediate parent state, we can just
648 : * reuse the record instead of going through a palloc/pfree
649 : * pushup (this works since it's all in TopTransactionContext
650 : * anyway). We have to re-link it into the parent level,
651 : * though, and that might mean pushing a new entry into the
652 : * pgStatXactStack.
653 : */
654 : PgStat_SubXactStatus *upper_xact_state;
655 :
656 2276 : upper_xact_state = pgstat_get_xact_stack_level(nestDepth - 1);
657 2276 : trans->next = upper_xact_state->first;
658 2276 : upper_xact_state->first = trans;
659 2276 : trans->nest_level = nestDepth - 1;
660 : }
661 : }
662 : else
663 : {
664 : /*
665 : * On abort, update top-level tabstat counts, then forget the
666 : * subtransaction
667 : */
668 :
669 : /* first restore values obliterated by truncate/drop */
670 1660 : restore_truncdrop_counters(trans);
671 : /* count attempted actions regardless of commit/abort */
672 1660 : tabstat->counts.tuples_inserted += trans->tuples_inserted;
673 1660 : tabstat->counts.tuples_updated += trans->tuples_updated;
674 1660 : tabstat->counts.tuples_deleted += trans->tuples_deleted;
675 : /* inserted tuples are dead, deleted tuples are unaffected */
676 1660 : tabstat->counts.delta_dead_tuples +=
677 1660 : trans->tuples_inserted + trans->tuples_updated;
678 1660 : tabstat->trans = trans->upper;
679 1660 : pfree(trans);
680 : }
681 : }
682 8442 : }
683 :
684 : /*
685 : * Generate 2PC records for all the pending transaction-dependent relation
686 : * stats.
687 : */
688 : void
689 596 : AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
690 : {
691 : PgStat_TableXactStatus *trans;
692 :
693 1456 : for (trans = xact_state->first; trans != NULL; trans = trans->next)
694 : {
695 : PgStat_TableStatus *tabstat PG_USED_FOR_ASSERTS_ONLY;
696 : TwoPhasePgStatRecord record;
697 :
698 : Assert(trans->nest_level == 1);
699 : Assert(trans->upper == NULL);
700 860 : tabstat = trans->parent;
701 : Assert(tabstat->trans == trans);
702 :
703 860 : record.tuples_inserted = trans->tuples_inserted;
704 860 : record.tuples_updated = trans->tuples_updated;
705 860 : record.tuples_deleted = trans->tuples_deleted;
706 860 : record.inserted_pre_truncdrop = trans->inserted_pre_truncdrop;
707 860 : record.updated_pre_truncdrop = trans->updated_pre_truncdrop;
708 860 : record.deleted_pre_truncdrop = trans->deleted_pre_truncdrop;
709 860 : record.id = tabstat->id;
710 860 : record.shared = tabstat->shared;
711 860 : record.truncdropped = trans->truncdropped;
712 :
713 860 : RegisterTwoPhaseRecord(TWOPHASE_RM_PGSTAT_ID, 0,
714 : &record, sizeof(TwoPhasePgStatRecord));
715 : }
716 596 : }
717 :
718 : /*
719 : * All we need do here is unlink the transaction stats state from the
720 : * nontransactional state. The nontransactional action counts will be
721 : * reported to the stats system immediately, while the effects on live and
722 : * dead tuple counts are preserved in the 2PC state file.
723 : *
724 : * Note: AtEOXact_PgStat_Relations is not called during PREPARE.
725 : */
726 : void
727 596 : PostPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
728 : {
729 : PgStat_TableXactStatus *trans;
730 :
731 1456 : for (trans = xact_state->first; trans != NULL; trans = trans->next)
732 : {
733 : PgStat_TableStatus *tabstat;
734 :
735 860 : tabstat = trans->parent;
736 860 : tabstat->trans = NULL;
737 : }
738 596 : }
739 :
740 : /*
741 : * 2PC processing routine for COMMIT PREPARED case.
742 : *
743 : * Load the saved counts into our local pgstats state.
744 : */
745 : void
746 714 : pgstat_twophase_postcommit(FullTransactionId fxid, uint16 info,
747 : void *recdata, uint32 len)
748 : {
749 714 : TwoPhasePgStatRecord *rec = (TwoPhasePgStatRecord *) recdata;
750 : PgStat_TableStatus *pgstat_info;
751 :
752 : /* Find or create a tabstat entry for the rel */
753 714 : pgstat_info = pgstat_prep_relation_pending(rec->id, rec->shared);
754 :
755 : /* Same math as in AtEOXact_PgStat, commit case */
756 714 : pgstat_info->counts.tuples_inserted += rec->tuples_inserted;
757 714 : pgstat_info->counts.tuples_updated += rec->tuples_updated;
758 714 : pgstat_info->counts.tuples_deleted += rec->tuples_deleted;
759 714 : pgstat_info->counts.truncdropped = rec->truncdropped;
760 714 : if (rec->truncdropped)
761 : {
762 : /* forget live/dead stats seen by backend thus far */
763 24 : pgstat_info->counts.delta_live_tuples = 0;
764 24 : pgstat_info->counts.delta_dead_tuples = 0;
765 : }
766 714 : pgstat_info->counts.delta_live_tuples +=
767 714 : rec->tuples_inserted - rec->tuples_deleted;
768 714 : pgstat_info->counts.delta_dead_tuples +=
769 714 : rec->tuples_updated + rec->tuples_deleted;
770 714 : pgstat_info->counts.changed_tuples +=
771 714 : rec->tuples_inserted + rec->tuples_updated +
772 714 : rec->tuples_deleted;
773 714 : }
774 :
775 : /*
776 : * 2PC processing routine for ROLLBACK PREPARED case.
777 : *
778 : * Load the saved counts into our local pgstats state, but treat them
779 : * as aborted.
780 : */
781 : void
782 170 : pgstat_twophase_postabort(FullTransactionId fxid, uint16 info,
783 : void *recdata, uint32 len)
784 : {
785 170 : TwoPhasePgStatRecord *rec = (TwoPhasePgStatRecord *) recdata;
786 : PgStat_TableStatus *pgstat_info;
787 :
788 : /* Find or create a tabstat entry for the rel */
789 170 : pgstat_info = pgstat_prep_relation_pending(rec->id, rec->shared);
790 :
791 : /* Same math as in AtEOXact_PgStat, abort case */
792 170 : if (rec->truncdropped)
793 : {
794 16 : rec->tuples_inserted = rec->inserted_pre_truncdrop;
795 16 : rec->tuples_updated = rec->updated_pre_truncdrop;
796 16 : rec->tuples_deleted = rec->deleted_pre_truncdrop;
797 : }
798 170 : pgstat_info->counts.tuples_inserted += rec->tuples_inserted;
799 170 : pgstat_info->counts.tuples_updated += rec->tuples_updated;
800 170 : pgstat_info->counts.tuples_deleted += rec->tuples_deleted;
801 170 : pgstat_info->counts.delta_dead_tuples +=
802 170 : rec->tuples_inserted + rec->tuples_updated;
803 170 : }
804 :
805 : /*
806 : * Flush out pending stats for the entry
807 : *
808 : * If nowait is true and the lock could not be immediately acquired, returns
809 : * false without flushing the entry. Otherwise returns true.
810 : *
811 : * Some of the stats are copied to the corresponding pending database stats
812 : * entry when successfully flushing.
813 : */
814 : bool
815 1951788 : pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
816 : {
817 : Oid dboid;
818 : PgStat_TableStatus *lstats; /* pending stats entry */
819 : PgStatShared_Relation *shtabstats;
820 : PgStat_StatTabEntry *tabentry; /* table entry of shared stats */
821 : PgStat_StatDBEntry *dbentry; /* pending database entry */
822 :
823 1951788 : dboid = entry_ref->shared_entry->key.dboid;
824 1951788 : lstats = (PgStat_TableStatus *) entry_ref->pending;
825 1951788 : shtabstats = (PgStatShared_Relation *) entry_ref->shared_stats;
826 :
827 : /*
828 : * Ignore entries that didn't accumulate any actual counts, such as
829 : * indexes that were opened by the planner but not used.
830 : */
831 1951788 : if (pg_memory_is_all_zeros(&lstats->counts,
832 : sizeof(struct PgStat_TableCounts)))
833 5586 : return true;
834 :
835 1946202 : if (!pgstat_lock_entry(entry_ref, nowait))
836 10 : return false;
837 :
838 : /* add the values to the shared entry. */
839 1946192 : tabentry = &shtabstats->stats;
840 :
841 1946192 : tabentry->numscans += lstats->counts.numscans;
842 1946192 : if (lstats->counts.numscans)
843 : {
844 1036790 : TimestampTz t = GetCurrentTransactionStopTimestamp();
845 :
846 1036790 : if (t > tabentry->lastscan)
847 1018788 : tabentry->lastscan = t;
848 : }
849 1946192 : tabentry->tuples_returned += lstats->counts.tuples_returned;
850 1946192 : tabentry->tuples_fetched += lstats->counts.tuples_fetched;
851 1946192 : tabentry->tuples_inserted += lstats->counts.tuples_inserted;
852 1946192 : tabentry->tuples_updated += lstats->counts.tuples_updated;
853 1946192 : tabentry->tuples_deleted += lstats->counts.tuples_deleted;
854 1946192 : tabentry->tuples_hot_updated += lstats->counts.tuples_hot_updated;
855 1946192 : tabentry->tuples_newpage_updated += lstats->counts.tuples_newpage_updated;
856 :
857 : /*
858 : * If table was truncated/dropped, first reset the live/dead counters.
859 : */
860 1946192 : if (lstats->counts.truncdropped)
861 : {
862 818 : tabentry->live_tuples = 0;
863 818 : tabentry->dead_tuples = 0;
864 818 : tabentry->ins_since_vacuum = 0;
865 : }
866 :
867 1946192 : tabentry->live_tuples += lstats->counts.delta_live_tuples;
868 1946192 : tabentry->dead_tuples += lstats->counts.delta_dead_tuples;
869 1946192 : tabentry->mod_since_analyze += lstats->counts.changed_tuples;
870 :
871 : /*
872 : * Using tuples_inserted to update ins_since_vacuum does mean that we'll
873 : * track aborted inserts too. This isn't ideal, but otherwise probably
874 : * not worth adding an extra field for. It may just amount to autovacuums
875 : * triggering for inserts more often than they maybe should, which is
876 : * probably not going to be common enough to be too concerned about here.
877 : */
878 1946192 : tabentry->ins_since_vacuum += lstats->counts.tuples_inserted;
879 :
880 1946192 : tabentry->blocks_fetched += lstats->counts.blocks_fetched;
881 1946192 : tabentry->blocks_hit += lstats->counts.blocks_hit;
882 :
883 : /* Clamp live_tuples in case of negative delta_live_tuples */
884 1946192 : tabentry->live_tuples = Max(tabentry->live_tuples, 0);
885 : /* Likewise for dead_tuples */
886 1946192 : tabentry->dead_tuples = Max(tabentry->dead_tuples, 0);
887 :
888 1946192 : pgstat_unlock_entry(entry_ref);
889 :
890 : /* The entry was successfully flushed, add the same to database stats */
891 1946192 : dbentry = pgstat_prep_database_pending(dboid);
892 1946192 : dbentry->tuples_returned += lstats->counts.tuples_returned;
893 1946192 : dbentry->tuples_fetched += lstats->counts.tuples_fetched;
894 1946192 : dbentry->tuples_inserted += lstats->counts.tuples_inserted;
895 1946192 : dbentry->tuples_updated += lstats->counts.tuples_updated;
896 1946192 : dbentry->tuples_deleted += lstats->counts.tuples_deleted;
897 1946192 : dbentry->blocks_fetched += lstats->counts.blocks_fetched;
898 1946192 : dbentry->blocks_hit += lstats->counts.blocks_hit;
899 :
900 1946192 : return true;
901 : }
902 :
903 : void
904 2021278 : pgstat_relation_delete_pending_cb(PgStat_EntryRef *entry_ref)
905 : {
906 2021278 : PgStat_TableStatus *pending = (PgStat_TableStatus *) entry_ref->pending;
907 :
908 2021278 : if (pending->relation)
909 1887130 : pgstat_unlink_relation(pending->relation);
910 2021278 : }
911 :
912 : void
913 17542 : pgstat_relation_reset_timestamp_cb(PgStatShared_Common *header, TimestampTz ts)
914 : {
915 17542 : ((PgStatShared_Relation *) header)->stats.stat_reset_time = ts;
916 17542 : }
917 :
918 : /*
919 : * Find or create a PgStat_TableStatus entry for rel. New entry is created and
920 : * initialized if not exists.
921 : */
922 : static PgStat_TableStatus *
923 2120218 : pgstat_prep_relation_pending(Oid rel_id, bool isshared)
924 : {
925 : PgStat_EntryRef *entry_ref;
926 : PgStat_TableStatus *pending;
927 :
928 2120218 : entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_RELATION,
929 : isshared ? InvalidOid : MyDatabaseId,
930 : rel_id, NULL);
931 2120218 : pending = entry_ref->pending;
932 2120218 : pending->id = rel_id;
933 2120218 : pending->shared = isshared;
934 :
935 2120218 : return pending;
936 : }
937 :
938 : /*
939 : * add a new (sub)transaction state record
940 : */
941 : static void
942 719690 : add_tabstat_xact_level(PgStat_TableStatus *pgstat_info, int nest_level)
943 : {
944 : PgStat_SubXactStatus *xact_state;
945 : PgStat_TableXactStatus *trans;
946 :
947 : /*
948 : * If this is the first rel to be modified at the current nest level, we
949 : * first have to push a transaction stack entry.
950 : */
951 719690 : xact_state = pgstat_get_xact_stack_level(nest_level);
952 :
953 : /* Now make a per-table stack entry */
954 : trans = (PgStat_TableXactStatus *)
955 719690 : MemoryContextAllocZero(TopTransactionContext,
956 : sizeof(PgStat_TableXactStatus));
957 719690 : trans->nest_level = nest_level;
958 719690 : trans->upper = pgstat_info->trans;
959 719690 : trans->parent = pgstat_info;
960 719690 : trans->next = xact_state->first;
961 719690 : xact_state->first = trans;
962 719690 : pgstat_info->trans = trans;
963 719690 : }
964 :
965 : /*
966 : * Add a new (sub)transaction record if needed.
967 : */
968 : static void
969 20806580 : ensure_tabstat_xact_level(PgStat_TableStatus *pgstat_info)
970 : {
971 20806580 : int nest_level = GetCurrentTransactionNestLevel();
972 :
973 20806580 : if (pgstat_info->trans == NULL ||
974 20093412 : pgstat_info->trans->nest_level != nest_level)
975 719690 : add_tabstat_xact_level(pgstat_info, nest_level);
976 20806580 : }
977 :
978 : /*
979 : * Whenever a table is truncated/dropped, we save its i/u/d counters so that
980 : * they can be cleared, and if the (sub)xact that executed the truncate/drop
981 : * later aborts, the counters can be restored to the saved (pre-truncate/drop)
982 : * values.
983 : *
984 : * Note that for truncate we do this on the first truncate in any particular
985 : * subxact level only.
986 : */
987 : static void
988 4952 : save_truncdrop_counters(PgStat_TableXactStatus *trans, bool is_drop)
989 : {
990 4952 : if (!trans->truncdropped || is_drop)
991 : {
992 4854 : trans->inserted_pre_truncdrop = trans->tuples_inserted;
993 4854 : trans->updated_pre_truncdrop = trans->tuples_updated;
994 4854 : trans->deleted_pre_truncdrop = trans->tuples_deleted;
995 4854 : trans->truncdropped = true;
996 : }
997 4952 : }
998 :
999 : /*
1000 : * restore counters when a truncate aborts
1001 : */
1002 : static void
1003 25200 : restore_truncdrop_counters(PgStat_TableXactStatus *trans)
1004 : {
1005 25200 : if (trans->truncdropped)
1006 : {
1007 336 : trans->tuples_inserted = trans->inserted_pre_truncdrop;
1008 336 : trans->tuples_updated = trans->updated_pre_truncdrop;
1009 336 : trans->tuples_deleted = trans->deleted_pre_truncdrop;
1010 : }
1011 25200 : }
|