Age Owner 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
368 andres 59 GIC 214 : pgstat_copy_relation_stats(Relation dst, Relation src)
368 andres 60 ECB : {
61 : PgStat_StatTabEntry *srcstats;
62 : PgStatShared_Relation *dstshstats;
63 : PgStat_EntryRef *dst_ref;
64 :
368 andres 65 GIC 214 : srcstats = pgstat_fetch_stat_tabentry_ext(src->rd_rel->relisshared,
368 andres 66 ECB : RelationGetRelid(src));
368 andres 67 GIC 214 : if (!srcstats)
368 andres 68 CBC 117 : return;
368 andres 69 ECB :
368 andres 70 GIC 97 : dst_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION,
368 andres 71 CBC 97 : dst->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
368 andres 72 ECB : RelationGetRelid(dst),
73 : false);
74 :
368 andres 75 GIC 97 : dstshstats = (PgStatShared_Relation *) dst_ref->shared_stats;
368 andres 76 CBC 97 : dstshstats->stats = *srcstats;
368 andres 77 ECB :
368 andres 78 GIC 97 : pgstat_unlock_entry(dst_ref);
368 andres 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
368 andres 93 GIC 28770326 : pgstat_init_relation(Relation rel)
384 andres 94 ECB : {
384 andres 95 GIC 28770326 : char relkind = rel->rd_rel->relkind;
384 andres 96 ECB :
97 : /*
98 : * We only count stats for relations with storage and partitioned tables
99 : */
384 andres 100 GIC 28770326 : if (!RELKIND_HAS_STORAGE(relkind) && relkind != RELKIND_PARTITIONED_TABLE)
384 andres 101 ECB : {
368 andres 102 GIC 158903 : rel->pgstat_enabled = false;
384 andres 103 CBC 158903 : rel->pgstat_info = NULL;
104 158903 : return;
384 andres 105 ECB : }
106 :
368 andres 107 GIC 28611423 : if (!pgstat_track_counts)
384 andres 108 ECB : {
368 andres 109 GIC 180 : if (rel->pgstat_info)
368 andres 110 CBC 10 : pgstat_unlink_relation(rel);
368 andres 111 ECB :
112 : /* We're not counting at all */
368 andres 113 GIC 180 : rel->pgstat_enabled = false;
384 andres 114 CBC 180 : rel->pgstat_info = NULL;
115 180 : return;
384 andres 116 ECB : }
117 :
368 andres 118 GIC 28611243 : rel->pgstat_enabled = true;
368 andres 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
368 andres 133 GIC 789409 : pgstat_assoc_relation(Relation rel)
368 andres 134 ECB : {
368 andres 135 GIC 789409 : Assert(rel->pgstat_enabled);
368 andres 136 CBC 789409 : Assert(rel->pgstat_info == NULL);
384 andres 137 ECB :
138 : /* Else find or make the PgStat_TableStatus entry, and update link */
368 andres 139 GIC 1578818 : rel->pgstat_info = pgstat_prep_relation_pending(RelationGetRelid(rel),
368 andres 140 CBC 789409 : rel->rd_rel->relisshared);
368 andres 141 ECB :
142 : /* don't allow link a stats to multiple relcache entries */
368 andres 143 GIC 789409 : Assert(rel->pgstat_info->relation == NULL);
368 andres 144 ECB :
145 : /* mark this relation as the owner */
368 andres 146 GIC 789409 : rel->pgstat_info->relation = rel;
368 andres 147 CBC 789409 : }
368 andres 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
368 andres 154 GIC 1393143 : pgstat_unlink_relation(Relation rel)
368 andres 155 ECB : {
156 : /* remove the link to stats info if any */
368 andres 157 GIC 1393143 : if (rel->pgstat_info == NULL)
368 andres 158 CBC 603734 : return;
368 andres 159 ECB :
160 : /* link sanity check */
368 andres 161 GIC 789409 : Assert(rel->pgstat_info->relation == rel);
368 andres 162 CBC 789409 : rel->pgstat_info->relation = NULL;
163 789409 : rel->pgstat_info = NULL;
384 andres 164 ECB : }
165 :
166 : /*
167 : * Ensure that stats are dropped if transaction aborts.
168 : */
169 : void
368 andres 170 GIC 164486 : pgstat_create_relation(Relation rel)
384 andres 171 ECB : {
368 andres 172 GIC 164486 : pgstat_create_transactional(PGSTAT_KIND_RELATION,
368 andres 173 CBC 164486 : rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
368 andres 174 ECB : RelationGetRelid(rel));
368 andres 175 GIC 164486 : }
384 andres 176 ECB :
177 : /*
178 : * Ensure that stats are dropped if transaction commits.
179 : */
180 : void
368 andres 181 GIC 29450 : pgstat_drop_relation(Relation rel)
368 andres 182 ECB : {
368 andres 183 GIC 29450 : int nest_level = GetCurrentTransactionNestLevel();
368 andres 184 ECB : PgStat_TableStatus *pgstat_info;
185 :
368 andres 186 GIC 29450 : pgstat_drop_transactional(PGSTAT_KIND_RELATION,
368 andres 187 CBC 29450 : rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
368 andres 188 ECB : RelationGetRelid(rel));
189 :
368 andres 190 GIC 29450 : if (!pgstat_should_count_relation(rel))
368 andres 191 CBC 1961 : return;
368 andres 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 : */
368 andres 197 GIC 27489 : pgstat_info = rel->pgstat_info;
368 andres 198 CBC 27489 : if (pgstat_info->trans &&
199 377 : pgstat_info->trans->nest_level == nest_level)
368 andres 200 ECB : {
368 andres 201 GIC 374 : save_truncdrop_counters(pgstat_info->trans, true);
368 andres 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;
368 andres 205 ECB : }
206 : }
207 :
208 : /*
209 : * Report that the table was just vacuumed and flush IO statistics.
210 : */
211 : void
384 andres 212 GIC 36956 : pgstat_report_vacuum(Oid tableoid, bool shared,
384 andres 213 ECB : PgStat_Counter livetuples, PgStat_Counter deadtuples)
214 : {
215 : PgStat_EntryRef *entry_ref;
216 : PgStatShared_Relation *shtabentry;
217 : PgStat_StatTabEntry *tabentry;
368 andres 218 GIC 36956 : Oid dboid = (shared ? InvalidOid : MyDatabaseId);
368 andres 219 ECB : TimestampTz ts;
220 :
368 andres 221 GIC 36956 : if (!pgstat_track_counts)
384 andres 222 LBC 0 : return;
384 andres 223 EUB :
224 : /* Store the data in the table's hash table entry. */
368 andres 225 GIC 36956 : ts = GetCurrentTimestamp();
368 andres 226 ECB :
227 : /* block acquiring lock for the same reason as pgstat_report_autovac() */
368 andres 228 GIC 36956 : entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION,
368 andres 229 ECB : dboid, tableoid, false);
230 :
368 andres 231 GIC 36956 : shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats;
368 andres 232 CBC 36956 : tabentry = &shtabentry->stats;
368 andres 233 ECB :
124 michael 234 GNC 36956 : tabentry->live_tuples = livetuples;
235 36956 : tabentry->dead_tuples = deadtuples;
368 andres 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 : */
124 michael 247 GNC 36956 : tabentry->ins_since_vacuum = 0;
368 andres 248 ECB :
368 andres 249 GIC 36956 : if (IsAutoVacuumWorkerProcess())
368 andres 250 ECB : {
124 michael 251 GNC 75 : tabentry->last_autovacuum_time = ts;
252 75 : tabentry->autovacuum_count++;
368 andres 253 ECB : }
254 : else
255 : {
124 michael 256 GNC 36881 : tabentry->last_vacuum_time = ts;
368 andres 257 CBC 36881 : tabentry->vacuum_count++;
368 andres 258 ECB : }
259 :
368 andres 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 : */
60 andres 268 GNC 36956 : pgstat_flush_io(false);
384 andres 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
384 andres 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;
368 285 23730 : Oid dboid = (rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId);
286 :
368 andres 287 CBC 23730 : if (!pgstat_track_counts)
384 andres 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
384 andres 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
368 296 : * double-counted after commit. (This approach also ensures that the
368 andres 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 : */
368 andres 302 GIC 23730 : if (pgstat_should_count_relation(rel) &&
384 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;
384 andres 311 ECB : }
312 : /* count stuff inserted by already-aborted subxacts, too */
16 michael 313 GNC 23393 : deadtuples -= rel->pgstat_info->counts.delta_dead_tuples;
314 : /* Since ANALYZE's counts are estimates, we could have underflowed */
384 andres 315 GIC 23393 : livetuples = Max(livetuples, 0);
384 andres 316 CBC 23393 : deadtuples = Max(deadtuples, 0);
317 : }
384 andres 318 ECB :
368 319 : /* block acquiring lock for the same reason as pgstat_report_autovac() */
368 andres 320 GIC 23730 : entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, dboid,
321 : RelationGetRelid(rel),
368 andres 322 ECB : false);
323 : /* can't get dropped while accessed */
368 andres 324 CBC 23730 : Assert(entry_ref != NULL && entry_ref->shared_stats != NULL);
368 andres 325 ECB :
368 andres 326 GIC 23730 : shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats;
327 23730 : tabentry = &shtabentry->stats;
328 :
124 michael 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
368 andres 335 ECB : * have no good way to estimate how many of those there were.
336 : */
368 andres 337 GIC 23730 : if (resetcounter)
124 michael 338 GNC 23708 : tabentry->mod_since_analyze = 0;
368 andres 339 ECB :
368 andres 340 GIC 23730 : if (IsAutoVacuumWorkerProcess())
341 : {
124 michael 342 GNC 158 : tabentry->last_autoanalyze_time = GetCurrentTimestamp();
343 158 : tabentry->autoanalyze_count++;
344 : }
345 : else
368 andres 346 ECB : {
124 michael 347 GNC 23572 : tabentry->last_analyze_time = GetCurrentTimestamp();
368 andres 348 GIC 23572 : tabentry->analyze_count++;
368 andres 349 ECB : }
350 :
368 andres 351 CBC 23730 : pgstat_unlock_entry(entry_ref);
352 :
353 : /* see pgstat_report_vacuum() */
60 andres 354 GNC 23730 : pgstat_flush_io(false);
384 andres 355 ECB : }
356 :
357 : /*
358 : * count a tuple insertion of n tuples
359 : */
360 : void
384 andres 361 GIC 12646279 : pgstat_count_heap_insert(Relation rel, PgStat_Counter n)
362 : {
368 andres 363 CBC 12646279 : if (pgstat_should_count_relation(rel))
364 : {
384 andres 365 GIC 11538822 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
384 andres 366 ECB :
384 andres 367 GIC 11538822 : ensure_tabstat_xact_level(pgstat_info);
368 11538822 : pgstat_info->trans->tuples_inserted += n;
369 : }
370 12646279 : }
371 :
372 : /*
370 andres 373 ECB : * count a tuple update
374 : */
384 375 : void
17 pg 376 GNC 418963 : pgstat_count_heap_update(Relation rel, bool hot, bool newpage)
384 andres 377 ECB : {
17 pg 378 GNC 418963 : Assert(!(hot && newpage));
379 :
368 andres 380 GIC 418963 : if (pgstat_should_count_relation(rel))
384 andres 381 ECB : {
384 andres 382 CBC 418961 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
383 :
384 418961 : ensure_tabstat_xact_level(pgstat_info);
384 andres 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)
16 michael 392 GNC 213801 : pgstat_info->counts.tuples_hot_updated++;
17 pg 393 205160 : else if (newpage)
16 michael 394 196182 : pgstat_info->counts.tuples_newpage_updated++;
384 andres 395 ECB : }
384 andres 396 GIC 418963 : }
384 andres 397 ECB :
398 : /*
370 399 : * count a tuple deletion
400 : */
384 401 : void
384 andres 402 GIC 1420203 : pgstat_count_heap_delete(Relation rel)
384 andres 403 ECB : {
368 andres 404 CBC 1420203 : if (pgstat_should_count_relation(rel))
405 : {
384 andres 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++;
384 andres 410 ECB : }
384 andres 411 CBC 1420203 : }
384 andres 412 ECB :
413 : /*
414 : * update tuple counters due to truncate
415 : */
416 : void
384 andres 417 GIC 1259 : pgstat_count_truncate(Relation rel)
418 : {
368 419 1259 : if (pgstat_should_count_relation(rel))
420 : {
384 andres 421 CBC 1259 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
422 :
423 1259 : ensure_tabstat_xact_level(pgstat_info);
368 andres 424 GIC 1259 : save_truncdrop_counters(pgstat_info->trans, false);
384 andres 425 CBC 1259 : pgstat_info->trans->tuples_inserted = 0;
384 andres 426 GIC 1259 : pgstat_info->trans->tuples_updated = 0;
384 andres 427 CBC 1259 : pgstat_info->trans->tuples_deleted = 0;
384 andres 428 ECB : }
384 andres 429 GIC 1259 : }
384 andres 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
384 andres 440 CBC 54297 : pgstat_update_heap_dead_tuples(Relation rel, int delta)
441 : {
368 442 54297 : if (pgstat_should_count_relation(rel))
384 andres 443 ECB : {
384 andres 444 CBC 54297 : PgStat_TableStatus *pgstat_info = rel->pgstat_info;
384 andres 445 ECB :
16 michael 446 GNC 54297 : pgstat_info->counts.delta_dead_tuples -= delta;
447 : }
384 andres 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 *
368 andres 457 GIC 4248 : pgstat_fetch_stat_tabentry(Oid relid)
458 : {
140 andres 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 : */
368 andres 466 ECB : PgStat_StatTabEntry *
368 andres 467 GIC 7032 : pgstat_fetch_stat_tabentry_ext(bool shared, Oid reloid)
368 andres 468 ECB : {
368 andres 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 : /*
370 andres 476 ECB : * find any existing PgStat_TableStatus entry for rel
477 : *
368 478 : * Find any existing PgStat_TableStatus entry for rel_id in the current
479 : * database. If not found, try finding from shared tables.
384 480 : *
368 481 : * If no entry found, return NULL, don't create a new one
482 : */
483 : PgStat_TableStatus *
384 andres 484 GIC 24 : find_tabstat_entry(Oid rel_id)
485 : {
486 : PgStat_EntryRef *entry_ref;
487 :
368 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)
368 andres 493 CBC 18 : return entry_ref->pending;
368 andres 494 GIC 6 : return NULL;
495 : }
496 :
384 andres 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
384 andres 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);
384 andres 515 CBC 709983 : Assert(trans->upper == NULL);
384 andres 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 */
384 andres 519 CBC 709983 : if (!isCommit)
368 andres 520 GIC 8148 : restore_truncdrop_counters(trans);
521 : /* count attempted actions regardless of commit/abort */
16 michael 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;
384 andres 525 CBC 709983 : if (isCommit)
384 andres 526 ECB : {
16 michael 527 GNC 701835 : tabstat->counts.truncdropped = trans->truncdropped;
384 andres 528 CBC 701835 : if (trans->truncdropped)
384 andres 529 ECB : {
530 : /* forget live/dead stats seen by backend thus far */
16 michael 531 GNC 1497 : tabstat->counts.delta_live_tuples = 0;
532 1497 : tabstat->counts.delta_dead_tuples = 0;
384 andres 533 ECB : }
534 : /* insert adds a live tuple, delete removes one */
16 michael 535 GNC 701835 : tabstat->counts.delta_live_tuples +=
384 andres 536 CBC 701835 : trans->tuples_inserted - trans->tuples_deleted;
384 andres 537 ECB : /* update and delete each create a dead tuple */
16 michael 538 GNC 701835 : tabstat->counts.delta_dead_tuples +=
384 andres 539 GIC 701835 : trans->tuples_updated + trans->tuples_deleted;
384 andres 540 ECB : /* insert, update, delete each count as one change event */
16 michael 541 GNC 701835 : tabstat->counts.changed_tuples +=
384 andres 542 GIC 701835 : trans->tuples_inserted + trans->tuples_updated +
543 701835 : trans->tuples_deleted;
384 andres 544 ECB : }
545 : else
546 : {
547 : /* inserted tuples are dead, deleted tuples are unaffected */
16 michael 548 GNC 8148 : tabstat->counts.delta_dead_tuples +=
384 andres 549 GIC 8148 : trans->tuples_inserted + trans->tuples_updated;
384 andres 550 ECB : /* an aborted xact generates no changed_tuple events */
551 : }
384 andres 552 CBC 709983 : tabstat->trans = NULL;
553 : }
384 andres 554 GIC 293944 : }
555 :
556 : /*
384 andres 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
384 andres 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 :
384 andres 573 CBC 3628 : next_trans = trans->next;
384 andres 574 GIC 3628 : Assert(trans->nest_level == nestDepth);
575 3628 : tabstat = trans->parent;
576 3628 : Assert(tabstat->trans == trans);
577 :
384 andres 578 CBC 3628 : if (isCommit)
579 : {
384 andres 580 GIC 2889 : if (trans->upper && trans->upper->nest_level == nestDepth - 1)
581 : {
384 andres 582 CBC 1730 : if (trans->truncdropped)
384 andres 583 ECB : {
584 : /* propagate the truncate/drop status one level up */
368 andres 585 CBC 12 : save_truncdrop_counters(trans->upper, false);
586 : /* replace upper xact stats with ours */
384 587 12 : trans->upper->tuples_inserted = trans->tuples_inserted;
384 andres 588 GIC 12 : trans->upper->tuples_updated = trans->tuples_updated;
384 andres 589 CBC 12 : trans->upper->tuples_deleted = trans->tuples_deleted;
590 : }
384 andres 591 ECB : else
592 : {
384 andres 593 GIC 1718 : trans->upper->tuples_inserted += trans->tuples_inserted;
384 andres 594 CBC 1718 : trans->upper->tuples_updated += trans->tuples_updated;
384 andres 595 GIC 1718 : trans->upper->tuples_deleted += trans->tuples_deleted;
384 andres 596 ECB : }
384 andres 597 CBC 1730 : tabstat->trans = trans->upper;
598 1730 : pfree(trans);
599 : }
600 : else
601 : {
384 andres 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 :
368 andres 612 GIC 1159 : upper_xact_state = pgstat_get_xact_stack_level(nestDepth - 1);
384 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 : /*
384 andres 621 ECB : * On abort, update top-level tabstat counts, then forget the
622 : * subtransaction
623 : */
624 :
625 : /* first restore values obliterated by truncate/drop */
368 andres 626 GIC 739 : restore_truncdrop_counters(trans);
627 : /* count attempted actions regardless of commit/abort */
16 michael 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 +=
384 andres 633 GIC 739 : trans->tuples_inserted + trans->tuples_updated;
634 739 : tabstat->trans = trans->upper;
384 andres 635 CBC 739 : pfree(trans);
636 : }
384 andres 637 ECB : }
384 andres 638 CBC 3336 : }
384 andres 639 ECB :
640 : /*
641 : * Generate 2PC records for all the pending transaction-dependent relation
642 : * stats.
643 : */
644 : void
384 andres 645 GIC 358 : AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
646 : {
384 andres 647 ECB : PgStat_TableXactStatus *trans;
648 :
384 andres 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 :
384 andres 654 CBC 439 : Assert(trans->nest_level == 1);
384 andres 655 GIC 439 : Assert(trans->upper == NULL);
656 439 : tabstat = trans->parent;
657 439 : Assert(tabstat->trans == trans);
384 andres 658 ECB :
384 andres 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;
384 andres 663 CBC 439 : record.updated_pre_truncdrop = trans->updated_pre_truncdrop;
664 439 : record.deleted_pre_truncdrop = trans->deleted_pre_truncdrop;
16 michael 665 GNC 439 : record.id = tabstat->id;
666 439 : record.shared = tabstat->shared;
667 439 : record.truncdropped = trans->truncdropped;
384 andres 668 ECB :
384 andres 669 CBC 439 : RegisterTwoPhaseRecord(TWOPHASE_RM_PGSTAT_ID, 0,
384 andres 670 ECB : &record, sizeof(TwoPhasePgStatRecord));
671 : }
384 andres 672 CBC 358 : }
384 andres 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
368 678 : * dead tuple counts are preserved in the 2PC state file.
679 : *
680 : * Note: AtEOXact_PgStat_Relations is not called during PREPARE.
384 681 : */
682 : void
384 andres 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;
384 andres 692 CBC 439 : tabstat->trans = NULL;
693 : }
384 andres 694 GIC 358 : }
695 :
384 andres 696 ECB : /*
697 : * 2PC processing routine for COMMIT PREPARED case.
698 : *
699 : * Load the saved counts into our local pgstats state.
700 : */
701 : void
384 andres 702 GIC 377 : pgstat_twophase_postcommit(TransactionId xid, uint16 info,
384 andres 703 ECB : void *recdata, uint32 len)
704 : {
384 andres 705 GIC 377 : TwoPhasePgStatRecord *rec = (TwoPhasePgStatRecord *) recdata;
706 : PgStat_TableStatus *pgstat_info;
707 :
708 : /* Find or create a tabstat entry for the rel */
16 michael 709 GNC 377 : pgstat_info = pgstat_prep_relation_pending(rec->id, rec->shared);
710 :
384 andres 711 ECB : /* Same math as in AtEOXact_PgStat, commit case */
16 michael 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 : {
384 andres 718 ECB : /* forget live/dead stats seen by backend thus far */
16 michael 719 GNC 2 : pgstat_info->counts.delta_live_tuples = 0;
720 2 : pgstat_info->counts.delta_dead_tuples = 0;
384 andres 721 ECB : }
16 michael 722 GNC 377 : pgstat_info->counts.delta_live_tuples +=
384 andres 723 CBC 377 : rec->tuples_inserted - rec->tuples_deleted;
16 michael 724 GNC 377 : pgstat_info->counts.delta_dead_tuples +=
384 andres 725 CBC 377 : rec->tuples_updated + rec->tuples_deleted;
16 michael 726 GNC 377 : pgstat_info->counts.changed_tuples +=
384 andres 727 GIC 377 : rec->tuples_inserted + rec->tuples_updated +
384 andres 728 CBC 377 : rec->tuples_deleted;
729 377 : }
730 :
384 andres 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
384 andres 738 CBC 62 : pgstat_twophase_postabort(TransactionId xid, uint16 info,
739 : void *recdata, uint32 len)
740 : {
384 andres 741 GIC 62 : TwoPhasePgStatRecord *rec = (TwoPhasePgStatRecord *) recdata;
742 : PgStat_TableStatus *pgstat_info;
743 :
744 : /* Find or create a tabstat entry for the rel */
16 michael 745 GNC 62 : pgstat_info = pgstat_prep_relation_pending(rec->id, rec->shared);
746 :
384 andres 747 ECB : /* Same math as in AtEOXact_PgStat, abort case */
16 michael 748 GNC 62 : if (rec->truncdropped)
749 : {
384 andres 750 CBC 4 : rec->tuples_inserted = rec->inserted_pre_truncdrop;
384 andres 751 GIC 4 : rec->tuples_updated = rec->updated_pre_truncdrop;
752 4 : rec->tuples_deleted = rec->deleted_pre_truncdrop;
753 : }
16 michael 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 +=
384 andres 758 GIC 62 : rec->tuples_inserted + rec->tuples_updated;
384 andres 759 CBC 62 : }
384 andres 760 ECB :
761 : /*
762 : * Flush out pending stats for the entry
368 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
368 andres 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 :
368 andres 780 CBC 717641 : dboid = entry_ref->shared_entry->key.dboid;
368 andres 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 : */
16 michael 788 GNC 717641 : if (memcmp(&lstats->counts, &all_zeroes,
368 andres 789 ECB : sizeof(PgStat_TableCounts)) == 0)
384 790 : {
368 andres 791 CBC 9588 : return true;
792 : }
793 :
368 andres 794 GIC 708053 : if (!pgstat_lock_entry(entry_ref, nowait))
795 3 : return false;
796 :
368 andres 797 ECB : /* add the values to the shared entry. */
368 andres 798 GIC 708050 : tabentry = &shtabstats->stats;
799 :
16 michael 800 GNC 708050 : tabentry->numscans += lstats->counts.numscans;
801 708050 : if (lstats->counts.numscans)
802 : {
177 andres 803 420107 : TimestampTz t = GetCurrentTransactionStopTimestamp();
804 :
805 420107 : if (t > tabentry->lastscan)
806 411854 : tabentry->lastscan = t;
807 : }
16 michael 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;
384 andres 815 ECB :
816 : /*
368 817 : * If table was truncated/dropped, first reset the live/dead counters.
384 818 : */
16 michael 819 GNC 708050 : if (lstats->counts.truncdropped)
368 andres 820 ECB : {
124 michael 821 GNC 222 : tabentry->live_tuples = 0;
822 222 : tabentry->dead_tuples = 0;
823 222 : tabentry->ins_since_vacuum = 0;
824 : }
384 andres 825 ECB :
16 michael 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 */
124 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 :
368 andres 838 CBC 708050 : pgstat_unlock_entry(entry_ref);
368 andres 839 ECB :
840 : /* The entry was successfully flushed, add the same to database stats */
368 andres 841 GIC 708050 : dbentry = pgstat_prep_database_pending(dboid);
16 michael 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 :
368 andres 850 GIC 708050 : return true;
384 andres 851 ECB : }
852 :
368 853 : void
368 andres 854 GIC 745357 : pgstat_relation_delete_pending_cb(PgStat_EntryRef *entry_ref)
384 andres 855 ECB : {
368 andres 856 GIC 745357 : PgStat_TableStatus *pending = (PgStat_TableStatus *) entry_ref->pending;
857 :
368 andres 858 CBC 745357 : if (pending->relation)
859 661286 : pgstat_unlink_relation(pending->relation);
384 860 745357 : }
384 andres 861 ECB :
862 : /*
368 863 : * Find or create a PgStat_TableStatus entry for rel. New entry is created and
864 : * initialized if not exists.
384 865 : */
866 : static PgStat_TableStatus *
368 andres 867 CBC 789848 : pgstat_prep_relation_pending(Oid rel_id, bool isshared)
868 : {
869 : PgStat_EntryRef *entry_ref;
870 : PgStat_TableStatus *pending;
384 andres 871 ECB :
368 andres 872 GIC 789848 : entry_ref = pgstat_prep_pending_entry(PGSTAT_KIND_RELATION,
368 andres 873 ECB : isshared ? InvalidOid : MyDatabaseId,
874 : rel_id, NULL);
368 andres 875 CBC 789848 : pending = entry_ref->pending;
16 michael 876 GNC 789848 : pending->id = rel_id;
877 789848 : pending->shared = isshared;
878 :
368 andres 879 GIC 789848 : return pending;
880 : }
881 :
882 : /*
883 : * add a new (sub)transaction state record
384 andres 884 ECB : */
885 : static void
384 andres 886 GIC 712891 : add_tabstat_xact_level(PgStat_TableStatus *pgstat_info, int nest_level)
887 : {
888 : PgStat_SubXactStatus *xact_state;
384 andres 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 : */
368 andres 895 GIC 712891 : xact_state = pgstat_get_xact_stack_level(nest_level);
384 andres 896 ECB :
897 : /* Now make a per-table stack entry */
898 : trans = (PgStat_TableXactStatus *)
384 andres 899 GIC 712891 : MemoryContextAllocZero(TopTransactionContext,
900 : sizeof(PgStat_TableXactStatus));
901 712891 : trans->nest_level = nest_level;
902 712891 : trans->upper = pgstat_info->trans;
384 andres 903 CBC 712891 : trans->parent = pgstat_info;
384 andres 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 : */
384 andres 912 ECB : static void
384 andres 913 GIC 13379245 : ensure_tabstat_xact_level(PgStat_TableStatus *pgstat_info)
914 : {
915 13379245 : int nest_level = GetCurrentTransactionNestLevel();
384 andres 916 ECB :
384 andres 917 GIC 13379245 : if (pgstat_info->trans == NULL ||
384 andres 918 CBC 12668627 : pgstat_info->trans->nest_level != nest_level)
919 712891 : add_tabstat_xact_level(pgstat_info, nest_level);
920 13379245 : }
384 andres 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
368 andres 932 CBC 1645 : save_truncdrop_counters(PgStat_TableXactStatus *trans, bool is_drop)
933 : {
384 934 1645 : if (!trans->truncdropped || is_drop)
384 andres 935 ECB : {
384 andres 936 CBC 1619 : trans->inserted_pre_truncdrop = trans->tuples_inserted;
937 1619 : trans->updated_pre_truncdrop = trans->tuples_updated;
384 andres 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
368 947 8887 : restore_truncdrop_counters(PgStat_TableXactStatus *trans)
948 : {
384 andres 949 CBC 8887 : if (trans->truncdropped)
950 : {
951 96 : trans->tuples_inserted = trans->inserted_pre_truncdrop;
384 andres 952 GIC 96 : trans->tuples_updated = trans->updated_pre_truncdrop;
384 andres 953 CBC 96 : trans->tuples_deleted = trans->deleted_pre_truncdrop;
384 andres 954 ECB : }
384 andres 955 CBC 8887 : }
|