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