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