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-2022, PostgreSQL Global Development Group
12 : *
13 : * IDENTIFICATION
14 : * src/backend/utils/activity/pgstat_relation.c
15 : * -------------------------------------------------------------------------
16 : */
17 :
18 : #include "postgres.h"
19 :
20 : #include "access/twophase_rmgr.h"
21 : #include "access/xact.h"
22 : #include "catalog/partition.h"
23 : #include "postmaster/autovacuum.h"
24 : #include "utils/memutils.h"
25 : #include "utils/pgstat_internal.h"
26 : #include "utils/rel.h"
27 : #include "utils/timestamp.h"
28 :
29 :
30 : /* Record that's written to 2PC state file when pgstat state is persisted */
31 : typedef struct TwoPhasePgStatRecord
32 : {
33 : PgStat_Counter tuples_inserted; /* tuples inserted in xact */
34 : PgStat_Counter tuples_updated; /* tuples updated in xact */
35 : PgStat_Counter tuples_deleted; /* tuples deleted in xact */
36 : /* tuples i/u/d prior to truncate/drop */
37 : PgStat_Counter inserted_pre_truncdrop;
38 : PgStat_Counter updated_pre_truncdrop;
39 : PgStat_Counter deleted_pre_truncdrop;
40 : Oid t_id; /* table's OID */
41 : bool t_shared; /* is it a shared catalog? */
42 : bool t_truncdropped; /* was the relation truncated/dropped? */
43 : } TwoPhasePgStatRecord;
44 :
45 :
46 : static PgStat_TableStatus *pgstat_prep_relation_pending(Oid rel_id, bool isshared);
47 : static void add_tabstat_xact_level(PgStat_TableStatus *pgstat_info, int nest_level);
48 : static void ensure_tabstat_xact_level(PgStat_TableStatus *pgstat_info);
49 : static void save_truncdrop_counters(PgStat_TableXactStatus *trans, bool is_drop);
50 : static void restore_truncdrop_counters(PgStat_TableXactStatus *trans);
51 :
52 :
53 : /*
54 : * Copy stats between relations. This is used for things like REINDEX
55 : * CONCURRENTLY.
56 : */
57 : void
58 424 : pgstat_copy_relation_stats(Relation dst, Relation src)
59 : {
60 : PgStat_StatTabEntry *srcstats;
61 : PgStatShared_Relation *dstshstats;
62 : PgStat_EntryRef *dst_ref;
63 :
64 424 : srcstats = pgstat_fetch_stat_tabentry_ext(src->rd_rel->relisshared,
65 : RelationGetRelid(src));
66 424 : if (!srcstats)
67 234 : return;
68 :
69 190 : dst_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION,
70 190 : dst->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
71 : RelationGetRelid(dst),
72 : false);
73 :
74 190 : dstshstats = (PgStatShared_Relation *) dst_ref->shared_stats;
75 190 : dstshstats->stats = *srcstats;
76 :
77 190 : 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 44848980 : pgstat_init_relation(Relation rel)
93 : {
94 44848980 : char relkind = rel->rd_rel->relkind;
95 :
96 : /*
97 : * We only count stats for relations with storage and partitioned tables
98 : */
99 44848980 : if (!RELKIND_HAS_STORAGE(relkind) && relkind != RELKIND_PARTITIONED_TABLE)
100 : {
101 396094 : rel->pgstat_enabled = false;
102 396094 : rel->pgstat_info = NULL;
103 396094 : return;
104 : }
105 :
106 44452886 : if (!pgstat_track_counts)
107 : {
108 360 : if (rel->pgstat_info)
109 20 : pgstat_unlink_relation(rel);
110 :
111 : /* We're not counting at all */
112 360 : rel->pgstat_enabled = false;
113 360 : rel->pgstat_info = NULL;
114 360 : return;
115 : }
116 :
117 44452526 : 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 1321114 : 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 2642228 : rel->pgstat_info = pgstat_prep_relation_pending(RelationGetRelid(rel),
139 1321114 : 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 1321114 : rel->pgstat_info->relation = rel;
146 1321114 : }
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 2458138 : pgstat_unlink_relation(Relation rel)
154 : {
155 : /* remove the link to stats info if any */
156 2458138 : if (rel->pgstat_info == NULL)
157 1137024 : return;
158 :
159 : /* link sanity check */
160 : Assert(rel->pgstat_info->relation == rel);
161 1321114 : rel->pgstat_info->relation = NULL;
162 1321114 : rel->pgstat_info = NULL;
163 : }
164 :
165 : /*
166 : * Ensure that stats are dropped if transaction aborts.
167 : */
168 : void
169 183472 : pgstat_create_relation(Relation rel)
170 : {
171 183472 : pgstat_create_transactional(PGSTAT_KIND_RELATION,
172 183472 : rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
173 : RelationGetRelid(rel));
174 183472 : }
175 :
176 : /*
177 : * Ensure that stats are dropped if transaction commits.
178 : */
179 : void
180 36110 : pgstat_drop_relation(Relation rel)
181 : {
182 36110 : int nest_level = GetCurrentTransactionNestLevel();
183 : PgStat_TableStatus *pgstat_info;
184 :
185 36110 : pgstat_drop_transactional(PGSTAT_KIND_RELATION,
186 36110 : rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
187 : RelationGetRelid(rel));
188 :
189 36110 : if (!pgstat_should_count_relation(rel))
190 2444 : 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 33666 : pgstat_info = rel->pgstat_info;
197 33666 : if (pgstat_info->trans &&
198 726 : pgstat_info->trans->nest_level == nest_level)
199 : {
200 720 : save_truncdrop_counters(pgstat_info->trans, true);
201 720 : pgstat_info->trans->tuples_inserted = 0;
202 720 : pgstat_info->trans->tuples_updated = 0;
203 720 : pgstat_info->trans->tuples_deleted = 0;
204 : }
205 : }
206 :
207 : /*
208 : * Report that the table was just vacuumed.
209 : */
210 : void
211 68024 : pgstat_report_vacuum(Oid tableoid, bool shared,
212 : PgStat_Counter livetuples, PgStat_Counter deadtuples)
213 : {
214 : PgStat_EntryRef *entry_ref;
215 : PgStatShared_Relation *shtabentry;
216 : PgStat_StatTabEntry *tabentry;
217 68024 : Oid dboid = (shared ? InvalidOid : MyDatabaseId);
218 : TimestampTz ts;
219 :
220 68024 : if (!pgstat_track_counts)
221 0 : return;
222 :
223 : /* Store the data in the table's hash table entry. */
224 68024 : ts = GetCurrentTimestamp();
225 :
226 : /* block acquiring lock for the same reason as pgstat_report_autovac() */
227 68024 : entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION,
228 : dboid, tableoid, false);
229 :
230 68024 : shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats;
231 68024 : tabentry = &shtabentry->stats;
232 :
233 68024 : tabentry->n_live_tuples = livetuples;
234 68024 : tabentry->n_dead_tuples = deadtuples;
235 :
236 : /*
237 : * It is quite possible that a non-aggressive VACUUM ended up skipping
238 : * various pages, however, we'll zero the insert counter here regardless.
239 : * It's currently used only to track when we need to perform an "insert"
240 : * autovacuum, which are mainly intended to freeze newly inserted tuples.
241 : * Zeroing this may just mean we'll not try to vacuum the table again
242 : * until enough tuples have been inserted to trigger another insert
243 : * autovacuum. An anti-wraparound autovacuum will catch any persistent
244 : * stragglers.
245 : */
246 68024 : tabentry->inserts_since_vacuum = 0;
247 :
248 68024 : if (IsAutoVacuumWorkerProcess())
249 : {
250 158 : tabentry->autovac_vacuum_timestamp = ts;
251 158 : tabentry->autovac_vacuum_count++;
252 : }
253 : else
254 : {
255 67866 : tabentry->vacuum_timestamp = ts;
256 67866 : tabentry->vacuum_count++;
257 : }
258 :
259 68024 : pgstat_unlock_entry(entry_ref);
260 : }
261 :
262 : /*
263 : * Report that the table was just analyzed.
264 : *
265 : * Caller must provide new live- and dead-tuples estimates, as well as a
266 : * flag indicating whether to reset the changes_since_analyze counter.
267 : */
268 : void
269 43820 : pgstat_report_analyze(Relation rel,
270 : PgStat_Counter livetuples, PgStat_Counter deadtuples,
271 : bool resetcounter)
272 : {
273 : PgStat_EntryRef *entry_ref;
274 : PgStatShared_Relation *shtabentry;
275 : PgStat_StatTabEntry *tabentry;
276 43820 : Oid dboid = (rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId);
277 :
278 43820 : if (!pgstat_track_counts)
279 0 : return;
280 :
281 : /*
282 : * Unlike VACUUM, ANALYZE might be running inside a transaction that has
283 : * already inserted and/or deleted rows in the target table. ANALYZE will
284 : * have counted such rows as live or dead respectively. Because we will
285 : * report our counts of such rows at transaction end, we should subtract
286 : * off these counts from the update we're making now, else they'll be
287 : * double-counted after commit. (This approach also ensures that the
288 : * shared stats entry ends up with the right numbers if we abort instead
289 : * of committing.)
290 : *
291 : * Waste no time on partitioned tables, though.
292 : */
293 43820 : if (pgstat_should_count_relation(rel) &&
294 43772 : rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
295 : {
296 : PgStat_TableXactStatus *trans;
297 :
298 43260 : for (trans = rel->pgstat_info->trans; trans; trans = trans->upper)
299 : {
300 108 : livetuples -= trans->tuples_inserted - trans->tuples_deleted;
301 108 : deadtuples -= trans->tuples_updated + trans->tuples_deleted;
302 : }
303 : /* count stuff inserted by already-aborted subxacts, too */
304 43152 : deadtuples -= rel->pgstat_info->t_counts.t_delta_dead_tuples;
305 : /* Since ANALYZE's counts are estimates, we could have underflowed */
306 43152 : livetuples = Max(livetuples, 0);
307 43152 : deadtuples = Max(deadtuples, 0);
308 : }
309 :
310 : /* block acquiring lock for the same reason as pgstat_report_autovac() */
311 43820 : entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, dboid,
312 : RelationGetRelid(rel),
313 : false);
314 : /* can't get dropped while accessed */
315 : Assert(entry_ref != NULL && entry_ref->shared_stats != NULL);
316 :
317 43820 : shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats;
318 43820 : tabentry = &shtabentry->stats;
319 :
320 43820 : tabentry->n_live_tuples = livetuples;
321 43820 : tabentry->n_dead_tuples = deadtuples;
322 :
323 : /*
324 : * If commanded, reset changes_since_analyze to zero. This forgets any
325 : * changes that were committed while the ANALYZE was in progress, but we
326 : * have no good way to estimate how many of those there were.
327 : */
328 43820 : if (resetcounter)
329 43776 : tabentry->changes_since_analyze = 0;
330 :
331 43820 : if (IsAutoVacuumWorkerProcess())
332 : {
333 320 : tabentry->autovac_analyze_timestamp = GetCurrentTimestamp();
334 320 : tabentry->autovac_analyze_count++;
335 : }
336 : else
337 : {
338 43500 : tabentry->analyze_timestamp = GetCurrentTimestamp();
339 43500 : tabentry->analyze_count++;
340 : }
341 :
342 43820 : pgstat_unlock_entry(entry_ref);
343 : }
344 :
345 : /*
346 : * count a tuple insertion of n tuples
347 : */
348 : void
349 23948232 : pgstat_count_heap_insert(Relation rel, PgStat_Counter n)
350 : {
351 23948232 : if (pgstat_should_count_relation(rel))
352 : {
353 21960476 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
354 :
355 21960476 : ensure_tabstat_xact_level(pgstat_info);
356 21960476 : pgstat_info->trans->tuples_inserted += n;
357 : }
358 23948232 : }
359 :
360 : /*
361 : * count a tuple update
362 : */
363 : void
364 738616 : pgstat_count_heap_update(Relation rel, bool hot)
365 : {
366 738616 : if (pgstat_should_count_relation(rel))
367 : {
368 738612 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
369 :
370 738612 : ensure_tabstat_xact_level(pgstat_info);
371 738612 : pgstat_info->trans->tuples_updated++;
372 :
373 : /* t_tuples_hot_updated is nontransactional, so just advance it */
374 738612 : if (hot)
375 369822 : pgstat_info->t_counts.t_tuples_hot_updated++;
376 : }
377 738616 : }
378 :
379 : /*
380 : * count a tuple deletion
381 : */
382 : void
383 2746418 : pgstat_count_heap_delete(Relation rel)
384 : {
385 2746418 : if (pgstat_should_count_relation(rel))
386 : {
387 2746418 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
388 :
389 2746418 : ensure_tabstat_xact_level(pgstat_info);
390 2746418 : pgstat_info->trans->tuples_deleted++;
391 : }
392 2746418 : }
393 :
394 : /*
395 : * update tuple counters due to truncate
396 : */
397 : void
398 2440 : pgstat_count_truncate(Relation rel)
399 : {
400 2440 : if (pgstat_should_count_relation(rel))
401 : {
402 2440 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
403 :
404 2440 : ensure_tabstat_xact_level(pgstat_info);
405 2440 : save_truncdrop_counters(pgstat_info->trans, false);
406 2440 : pgstat_info->trans->tuples_inserted = 0;
407 2440 : pgstat_info->trans->tuples_updated = 0;
408 2440 : pgstat_info->trans->tuples_deleted = 0;
409 : }
410 2440 : }
411 :
412 : /*
413 : * update dead-tuples count
414 : *
415 : * The semantics of this are that we are reporting the nontransactional
416 : * recovery of "delta" dead tuples; so t_delta_dead_tuples decreases
417 : * rather than increasing, and the change goes straight into the per-table
418 : * counter, not into transactional state.
419 : */
420 : void
421 100798 : pgstat_update_heap_dead_tuples(Relation rel, int delta)
422 : {
423 100798 : if (pgstat_should_count_relation(rel))
424 : {
425 100798 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
426 :
427 100798 : pgstat_info->t_counts.t_delta_dead_tuples -= delta;
428 : }
429 100798 : }
430 :
431 : /*
432 : * Support function for the SQL-callable pgstat* functions. Returns
433 : * the collected statistics for one table or NULL. NULL doesn't mean
434 : * that the table doesn't exist, just that there are no statistics, so the
435 : * caller is better off to report ZERO instead.
436 : */
437 : PgStat_StatTabEntry *
438 802 : pgstat_fetch_stat_tabentry(Oid relid)
439 : {
440 : PgStat_StatTabEntry *tabentry;
441 :
442 802 : tabentry = pgstat_fetch_stat_tabentry_ext(false, relid);
443 802 : if (tabentry != NULL)
444 746 : return tabentry;
445 :
446 : /*
447 : * If we didn't find it, maybe it's a shared table.
448 : */
449 56 : tabentry = pgstat_fetch_stat_tabentry_ext(true, relid);
450 56 : return tabentry;
451 : }
452 :
453 : /*
454 : * More efficient version of pgstat_fetch_stat_tabentry(), allowing to specify
455 : * whether the to-be-accessed table is a shared relation or not.
456 : */
457 : PgStat_StatTabEntry *
458 4922 : pgstat_fetch_stat_tabentry_ext(bool shared, Oid reloid)
459 : {
460 4922 : Oid dboid = (shared ? InvalidOid : MyDatabaseId);
461 :
462 4922 : return (PgStat_StatTabEntry *)
463 4922 : pgstat_fetch_entry(PGSTAT_KIND_RELATION, dboid, reloid);
464 : }
465 :
466 : /*
467 : * find any existing PgStat_TableStatus entry for rel
468 : *
469 : * Find any existing PgStat_TableStatus entry for rel_id in the current
470 : * database. If not found, try finding from shared tables.
471 : *
472 : * If no entry found, return NULL, don't create a new one
473 : */
474 : PgStat_TableStatus *
475 48 : find_tabstat_entry(Oid rel_id)
476 : {
477 : PgStat_EntryRef *entry_ref;
478 :
479 48 : entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_RELATION, MyDatabaseId, rel_id);
480 48 : if (!entry_ref)
481 12 : entry_ref = pgstat_fetch_pending_entry(PGSTAT_KIND_RELATION, InvalidOid, rel_id);
482 :
483 48 : if (entry_ref)
484 36 : return entry_ref->pending;
485 12 : return NULL;
486 : }
487 :
488 : /*
489 : * Perform relation stats specific end-of-transaction work. Helper for
490 : * AtEOXact_PgStat.
491 : *
492 : * Transfer transactional insert/update counts into the base tabstat entries.
493 : * We don't bother to free any of the transactional state, since it's all in
494 : * TopTransactionContext and will go away anyway.
495 : */
496 : void
497 540854 : AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit)
498 : {
499 : PgStat_TableXactStatus *trans;
500 :
501 1848594 : for (trans = xact_state->first; trans != NULL; trans = trans->next)
502 : {
503 : PgStat_TableStatus *tabstat;
504 :
505 : Assert(trans->nest_level == 1);
506 : Assert(trans->upper == NULL);
507 1307740 : tabstat = trans->parent;
508 : Assert(tabstat->trans == trans);
509 : /* restore pre-truncate/drop stats (if any) in case of aborted xact */
510 1307740 : if (!isCommit)
511 15884 : restore_truncdrop_counters(trans);
512 : /* count attempted actions regardless of commit/abort */
513 1307740 : tabstat->t_counts.t_tuples_inserted += trans->tuples_inserted;
514 1307740 : tabstat->t_counts.t_tuples_updated += trans->tuples_updated;
515 1307740 : tabstat->t_counts.t_tuples_deleted += trans->tuples_deleted;
516 1307740 : if (isCommit)
517 : {
518 1291856 : tabstat->t_counts.t_truncdropped = trans->truncdropped;
519 1291856 : if (trans->truncdropped)
520 : {
521 : /* forget live/dead stats seen by backend thus far */
522 2834 : tabstat->t_counts.t_delta_live_tuples = 0;
523 2834 : tabstat->t_counts.t_delta_dead_tuples = 0;
524 : }
525 : /* insert adds a live tuple, delete removes one */
526 1291856 : tabstat->t_counts.t_delta_live_tuples +=
527 1291856 : trans->tuples_inserted - trans->tuples_deleted;
528 : /* update and delete each create a dead tuple */
529 1291856 : tabstat->t_counts.t_delta_dead_tuples +=
530 1291856 : trans->tuples_updated + trans->tuples_deleted;
531 : /* insert, update, delete each count as one change event */
532 1291856 : tabstat->t_counts.t_changed_tuples +=
533 1291856 : trans->tuples_inserted + trans->tuples_updated +
534 1291856 : trans->tuples_deleted;
535 : }
536 : else
537 : {
538 : /* inserted tuples are dead, deleted tuples are unaffected */
539 15884 : tabstat->t_counts.t_delta_dead_tuples +=
540 15884 : trans->tuples_inserted + trans->tuples_updated;
541 : /* an aborted xact generates no changed_tuple events */
542 : }
543 1307740 : tabstat->trans = NULL;
544 : }
545 540854 : }
546 :
547 : /*
548 : * Perform relation stats specific end-of-sub-transaction work. Helper for
549 : * AtEOSubXact_PgStat.
550 : *
551 : * Transfer transactional insert/update counts into the next higher
552 : * subtransaction state.
553 : */
554 : void
555 5766 : AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth)
556 : {
557 : PgStat_TableXactStatus *trans;
558 : PgStat_TableXactStatus *next_trans;
559 :
560 12138 : for (trans = xact_state->first; trans != NULL; trans = next_trans)
561 : {
562 : PgStat_TableStatus *tabstat;
563 :
564 6372 : next_trans = trans->next;
565 : Assert(trans->nest_level == nestDepth);
566 6372 : tabstat = trans->parent;
567 : Assert(tabstat->trans == trans);
568 :
569 6372 : if (isCommit)
570 : {
571 4916 : if (trans->upper && trans->upper->nest_level == nestDepth - 1)
572 : {
573 3430 : if (trans->truncdropped)
574 : {
575 : /* propagate the truncate/drop status one level up */
576 24 : save_truncdrop_counters(trans->upper, false);
577 : /* replace upper xact stats with ours */
578 24 : trans->upper->tuples_inserted = trans->tuples_inserted;
579 24 : trans->upper->tuples_updated = trans->tuples_updated;
580 24 : trans->upper->tuples_deleted = trans->tuples_deleted;
581 : }
582 : else
583 : {
584 3406 : trans->upper->tuples_inserted += trans->tuples_inserted;
585 3406 : trans->upper->tuples_updated += trans->tuples_updated;
586 3406 : trans->upper->tuples_deleted += trans->tuples_deleted;
587 : }
588 3430 : tabstat->trans = trans->upper;
589 3430 : pfree(trans);
590 : }
591 : else
592 : {
593 : /*
594 : * When there isn't an immediate parent state, we can just
595 : * reuse the record instead of going through a palloc/pfree
596 : * pushup (this works since it's all in TopTransactionContext
597 : * anyway). We have to re-link it into the parent level,
598 : * though, and that might mean pushing a new entry into the
599 : * pgStatXactStack.
600 : */
601 : PgStat_SubXactStatus *upper_xact_state;
602 :
603 1486 : upper_xact_state = pgstat_get_xact_stack_level(nestDepth - 1);
604 1486 : trans->next = upper_xact_state->first;
605 1486 : upper_xact_state->first = trans;
606 1486 : trans->nest_level = nestDepth - 1;
607 : }
608 : }
609 : else
610 : {
611 : /*
612 : * On abort, update top-level tabstat counts, then forget the
613 : * subtransaction
614 : */
615 :
616 : /* first restore values obliterated by truncate/drop */
617 1456 : restore_truncdrop_counters(trans);
618 : /* count attempted actions regardless of commit/abort */
619 1456 : tabstat->t_counts.t_tuples_inserted += trans->tuples_inserted;
620 1456 : tabstat->t_counts.t_tuples_updated += trans->tuples_updated;
621 1456 : tabstat->t_counts.t_tuples_deleted += trans->tuples_deleted;
622 : /* inserted tuples are dead, deleted tuples are unaffected */
623 1456 : tabstat->t_counts.t_delta_dead_tuples +=
624 1456 : trans->tuples_inserted + trans->tuples_updated;
625 1456 : tabstat->trans = trans->upper;
626 1456 : pfree(trans);
627 : }
628 : }
629 5766 : }
630 :
631 : /*
632 : * Generate 2PC records for all the pending transaction-dependent relation
633 : * stats.
634 : */
635 : void
636 716 : AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
637 : {
638 : PgStat_TableXactStatus *trans;
639 :
640 1590 : for (trans = xact_state->first; trans != NULL; trans = trans->next)
641 : {
642 : PgStat_TableStatus *tabstat PG_USED_FOR_ASSERTS_ONLY;
643 : TwoPhasePgStatRecord record;
644 :
645 : Assert(trans->nest_level == 1);
646 : Assert(trans->upper == NULL);
647 874 : tabstat = trans->parent;
648 : Assert(tabstat->trans == trans);
649 :
650 874 : record.tuples_inserted = trans->tuples_inserted;
651 874 : record.tuples_updated = trans->tuples_updated;
652 874 : record.tuples_deleted = trans->tuples_deleted;
653 874 : record.inserted_pre_truncdrop = trans->inserted_pre_truncdrop;
654 874 : record.updated_pre_truncdrop = trans->updated_pre_truncdrop;
655 874 : record.deleted_pre_truncdrop = trans->deleted_pre_truncdrop;
656 874 : record.t_id = tabstat->t_id;
657 874 : record.t_shared = tabstat->t_shared;
658 874 : record.t_truncdropped = trans->truncdropped;
659 :
660 874 : RegisterTwoPhaseRecord(TWOPHASE_RM_PGSTAT_ID, 0,
661 : &record, sizeof(TwoPhasePgStatRecord));
662 : }
663 716 : }
664 :
665 : /*
666 : * All we need do here is unlink the transaction stats state from the
667 : * nontransactional state. The nontransactional action counts will be
668 : * reported to the stats system immediately, while the effects on live and
669 : * dead tuple counts are preserved in the 2PC state file.
670 : *
671 : * Note: AtEOXact_PgStat_Relations is not called during PREPARE.
672 : */
673 : void
674 716 : PostPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
675 : {
676 : PgStat_TableXactStatus *trans;
677 :
678 1590 : for (trans = xact_state->first; trans != NULL; trans = trans->next)
679 : {
680 : PgStat_TableStatus *tabstat;
681 :
682 874 : tabstat = trans->parent;
683 874 : tabstat->trans = NULL;
684 : }
685 716 : }
686 :
687 : /*
688 : * 2PC processing routine for COMMIT PREPARED case.
689 : *
690 : * Load the saved counts into our local pgstats state.
691 : */
692 : void
693 756 : pgstat_twophase_postcommit(TransactionId xid, uint16 info,
694 : void *recdata, uint32 len)
695 : {
696 756 : TwoPhasePgStatRecord *rec = (TwoPhasePgStatRecord *) recdata;
697 : PgStat_TableStatus *pgstat_info;
698 :
699 : /* Find or create a tabstat entry for the rel */
700 756 : pgstat_info = pgstat_prep_relation_pending(rec->t_id, rec->t_shared);
701 :
702 : /* Same math as in AtEOXact_PgStat, commit case */
703 756 : pgstat_info->t_counts.t_tuples_inserted += rec->tuples_inserted;
704 756 : pgstat_info->t_counts.t_tuples_updated += rec->tuples_updated;
705 756 : pgstat_info->t_counts.t_tuples_deleted += rec->tuples_deleted;
706 756 : pgstat_info->t_counts.t_truncdropped = rec->t_truncdropped;
707 756 : if (rec->t_truncdropped)
708 : {
709 : /* forget live/dead stats seen by backend thus far */
710 4 : pgstat_info->t_counts.t_delta_live_tuples = 0;
711 4 : pgstat_info->t_counts.t_delta_dead_tuples = 0;
712 : }
713 756 : pgstat_info->t_counts.t_delta_live_tuples +=
714 756 : rec->tuples_inserted - rec->tuples_deleted;
715 756 : pgstat_info->t_counts.t_delta_dead_tuples +=
716 756 : rec->tuples_updated + rec->tuples_deleted;
717 756 : pgstat_info->t_counts.t_changed_tuples +=
718 756 : rec->tuples_inserted + rec->tuples_updated +
719 756 : rec->tuples_deleted;
720 756 : }
721 :
722 : /*
723 : * 2PC processing routine for ROLLBACK PREPARED case.
724 : *
725 : * Load the saved counts into our local pgstats state, but treat them
726 : * as aborted.
727 : */
728 : void
729 114 : pgstat_twophase_postabort(TransactionId xid, uint16 info,
730 : void *recdata, uint32 len)
731 : {
732 114 : TwoPhasePgStatRecord *rec = (TwoPhasePgStatRecord *) recdata;
733 : PgStat_TableStatus *pgstat_info;
734 :
735 : /* Find or create a tabstat entry for the rel */
736 114 : pgstat_info = pgstat_prep_relation_pending(rec->t_id, rec->t_shared);
737 :
738 : /* Same math as in AtEOXact_PgStat, abort case */
739 114 : if (rec->t_truncdropped)
740 : {
741 8 : rec->tuples_inserted = rec->inserted_pre_truncdrop;
742 8 : rec->tuples_updated = rec->updated_pre_truncdrop;
743 8 : rec->tuples_deleted = rec->deleted_pre_truncdrop;
744 : }
745 114 : pgstat_info->t_counts.t_tuples_inserted += rec->tuples_inserted;
746 114 : pgstat_info->t_counts.t_tuples_updated += rec->tuples_updated;
747 114 : pgstat_info->t_counts.t_tuples_deleted += rec->tuples_deleted;
748 114 : pgstat_info->t_counts.t_delta_dead_tuples +=
749 114 : rec->tuples_inserted + rec->tuples_updated;
750 114 : }
751 :
752 : /*
753 : * Flush out pending stats for the entry
754 : *
755 : * If nowait is true, this function returns false if lock could not
756 : * immediately acquired, otherwise true is returned.
757 : *
758 : * Some of the stats are copied to the corresponding pending database stats
759 : * entry when successfully flushing.
760 : */
761 : bool
762 1185044 : pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
763 : {
764 : static const PgStat_TableCounts all_zeroes;
765 : Oid dboid;
766 : PgStat_TableStatus *lstats; /* pending stats entry */
767 : PgStatShared_Relation *shtabstats;
768 : PgStat_StatTabEntry *tabentry; /* table entry of shared stats */
769 : PgStat_StatDBEntry *dbentry; /* pending database entry */
770 :
771 1185044 : dboid = entry_ref->shared_entry->key.dboid;
772 1185044 : lstats = (PgStat_TableStatus *) entry_ref->pending;
773 1185044 : shtabstats = (PgStatShared_Relation *) entry_ref->shared_stats;
774 :
775 : /*
776 : * Ignore entries that didn't accumulate any actual counts, such as
777 : * indexes that were opened by the planner but not used.
778 : */
779 1185044 : if (memcmp(&lstats->t_counts, &all_zeroes,
780 : sizeof(PgStat_TableCounts)) == 0)
781 : {
782 16568 : return true;
783 : }
784 :
785 1168476 : if (!pgstat_lock_entry(entry_ref, nowait))
786 18 : return false;
787 :
788 : /* add the values to the shared entry. */
789 1168458 : tabentry = &shtabstats->stats;
790 :
791 1168458 : tabentry->numscans += lstats->t_counts.t_numscans;
792 1168458 : tabentry->tuples_returned += lstats->t_counts.t_tuples_returned;
793 1168458 : tabentry->tuples_fetched += lstats->t_counts.t_tuples_fetched;
794 1168458 : tabentry->tuples_inserted += lstats->t_counts.t_tuples_inserted;
795 1168458 : tabentry->tuples_updated += lstats->t_counts.t_tuples_updated;
796 1168458 : tabentry->tuples_deleted += lstats->t_counts.t_tuples_deleted;
797 1168458 : tabentry->tuples_hot_updated += lstats->t_counts.t_tuples_hot_updated;
798 :
799 : /*
800 : * If table was truncated/dropped, first reset the live/dead counters.
801 : */
802 1168458 : if (lstats->t_counts.t_truncdropped)
803 : {
804 372 : tabentry->n_live_tuples = 0;
805 372 : tabentry->n_dead_tuples = 0;
806 372 : tabentry->inserts_since_vacuum = 0;
807 : }
808 :
809 1168458 : tabentry->n_live_tuples += lstats->t_counts.t_delta_live_tuples;
810 1168458 : tabentry->n_dead_tuples += lstats->t_counts.t_delta_dead_tuples;
811 1168458 : tabentry->changes_since_analyze += lstats->t_counts.t_changed_tuples;
812 1168458 : tabentry->inserts_since_vacuum += lstats->t_counts.t_tuples_inserted;
813 1168458 : tabentry->blocks_fetched += lstats->t_counts.t_blocks_fetched;
814 1168458 : tabentry->blocks_hit += lstats->t_counts.t_blocks_hit;
815 :
816 : /* Clamp n_live_tuples in case of negative delta_live_tuples */
817 1168458 : tabentry->n_live_tuples = Max(tabentry->n_live_tuples, 0);
818 : /* Likewise for n_dead_tuples */
819 1168458 : tabentry->n_dead_tuples = Max(tabentry->n_dead_tuples, 0);
820 :
821 1168458 : pgstat_unlock_entry(entry_ref);
822 :
823 : /* The entry was successfully flushed, add the same to database stats */
824 1168458 : dbentry = pgstat_prep_database_pending(dboid);
825 1168458 : dbentry->n_tuples_returned += lstats->t_counts.t_tuples_returned;
826 1168458 : dbentry->n_tuples_fetched += lstats->t_counts.t_tuples_fetched;
827 1168458 : dbentry->n_tuples_inserted += lstats->t_counts.t_tuples_inserted;
828 1168458 : dbentry->n_tuples_updated += lstats->t_counts.t_tuples_updated;
829 1168458 : dbentry->n_tuples_deleted += lstats->t_counts.t_tuples_deleted;
830 1168458 : dbentry->n_blocks_fetched += lstats->t_counts.t_blocks_fetched;
831 1168458 : dbentry->n_blocks_hit += lstats->t_counts.t_blocks_hit;
832 :
833 1168458 : return true;
834 : }
835 :
836 : void
837 1219304 : pgstat_relation_delete_pending_cb(PgStat_EntryRef *entry_ref)
838 : {
839 1219304 : PgStat_TableStatus *pending = (PgStat_TableStatus *) entry_ref->pending;
840 :
841 1219304 : if (pending->relation)
842 1083262 : pgstat_unlink_relation(pending->relation);
843 1219304 : }
844 :
845 : /*
846 : * Find or create a PgStat_TableStatus entry for rel. New entry is created and
847 : * initialized if not exists.
848 : */
849 : static PgStat_TableStatus *
850 1321984 : pgstat_prep_relation_pending(Oid rel_id, bool isshared)
851 : {
852 : PgStat_EntryRef *entry_ref;
853 : PgStat_TableStatus *pending;
854 :
855 1321984 : entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_RELATION,
856 : isshared ? InvalidOid : MyDatabaseId,
857 : rel_id, NULL);
858 1321984 : pending = entry_ref->pending;
859 1321984 : pending->t_id = rel_id;
860 1321984 : pending->t_shared = isshared;
861 :
862 1321984 : return pending;
863 : }
864 :
865 : /*
866 : * add a new (sub)transaction state record
867 : */
868 : static void
869 1313500 : add_tabstat_xact_level(PgStat_TableStatus *pgstat_info, int nest_level)
870 : {
871 : PgStat_SubXactStatus *xact_state;
872 : PgStat_TableXactStatus *trans;
873 :
874 : /*
875 : * If this is the first rel to be modified at the current nest level, we
876 : * first have to push a transaction stack entry.
877 : */
878 1313500 : xact_state = pgstat_get_xact_stack_level(nest_level);
879 :
880 : /* Now make a per-table stack entry */
881 : trans = (PgStat_TableXactStatus *)
882 1313500 : MemoryContextAllocZero(TopTransactionContext,
883 : sizeof(PgStat_TableXactStatus));
884 1313500 : trans->nest_level = nest_level;
885 1313500 : trans->upper = pgstat_info->trans;
886 1313500 : trans->parent = pgstat_info;
887 1313500 : trans->next = xact_state->first;
888 1313500 : xact_state->first = trans;
889 1313500 : pgstat_info->trans = trans;
890 1313500 : }
891 :
892 : /*
893 : * Add a new (sub)transaction record if needed.
894 : */
895 : static void
896 25447946 : ensure_tabstat_xact_level(PgStat_TableStatus *pgstat_info)
897 : {
898 25447946 : int nest_level = GetCurrentTransactionNestLevel();
899 :
900 25447946 : if (pgstat_info->trans == NULL ||
901 24138956 : pgstat_info->trans->nest_level != nest_level)
902 1313500 : add_tabstat_xact_level(pgstat_info, nest_level);
903 25447946 : }
904 :
905 : /*
906 : * Whenever a table is truncated/dropped, we save its i/u/d counters so that
907 : * they can be cleared, and if the (sub)xact that executed the truncate/drop
908 : * later aborts, the counters can be restored to the saved (pre-truncate/drop)
909 : * values.
910 : *
911 : * Note that for truncate we do this on the first truncate in any particular
912 : * subxact level only.
913 : */
914 : static void
915 3184 : save_truncdrop_counters(PgStat_TableXactStatus *trans, bool is_drop)
916 : {
917 3184 : if (!trans->truncdropped || is_drop)
918 : {
919 3092 : trans->inserted_pre_truncdrop = trans->tuples_inserted;
920 3092 : trans->updated_pre_truncdrop = trans->tuples_updated;
921 3092 : trans->deleted_pre_truncdrop = trans->tuples_deleted;
922 3092 : trans->truncdropped = true;
923 : }
924 3184 : }
925 :
926 : /*
927 : * restore counters when a truncate aborts
928 : */
929 : static void
930 17340 : restore_truncdrop_counters(PgStat_TableXactStatus *trans)
931 : {
932 17340 : if (trans->truncdropped)
933 : {
934 202 : trans->tuples_inserted = trans->inserted_pre_truncdrop;
935 202 : trans->tuples_updated = trans->updated_pre_truncdrop;
936 202 : trans->tuples_deleted = trans->deleted_pre_truncdrop;
937 : }
938 17340 : }
|