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