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 relation statistics. It is kept
7 : : * separate from pgstat.c to enforce the line between the statistics access /
8 : : * storage implementation and the details about individual types of
9 : : * statistics.
10 : : *
11 : : * Copyright (c) 2001-2026, PostgreSQL Global Development Group
12 : : *
13 : : * IDENTIFICATION
14 : : * src/backend/utils/activity/pgstat_relation.c
15 : : * -------------------------------------------------------------------------
16 : : */
17 : :
18 : : #include "postgres.h"
19 : :
20 : : #include "access/twophase_rmgr.h"
21 : : #include "access/xact.h"
22 : : #include "catalog/catalog.h"
23 : : #include "utils/memutils.h"
24 : : #include "utils/pgstat_internal.h"
25 : : #include "utils/rel.h"
26 : : #include "utils/timestamp.h"
27 : :
28 : :
29 : : /* Record that's written to 2PC state file when pgstat state is persisted */
30 : : typedef struct TwoPhasePgStatRecord
31 : : {
32 : : PgStat_Counter tuples_inserted; /* tuples inserted in xact */
33 : : PgStat_Counter tuples_updated; /* tuples updated in xact */
34 : : PgStat_Counter tuples_deleted; /* tuples deleted in xact */
35 : : /* tuples i/u/d prior to truncate/drop */
36 : : PgStat_Counter inserted_pre_truncdrop;
37 : : PgStat_Counter updated_pre_truncdrop;
38 : : PgStat_Counter deleted_pre_truncdrop;
39 : : Oid id; /* table's OID */
40 : : bool shared; /* is it a shared catalog? */
41 : : bool truncdropped; /* was the relation truncated/dropped? */
42 : : } TwoPhasePgStatRecord;
43 : :
44 : :
45 : : static PgStat_RelationStatus *pgstat_prep_relation_pending(PgStat_Kind kind,
46 : : Oid rel_id, bool isshared);
47 : : static void add_tabstat_xact_level(PgStat_RelationStatus *pgstat_info, int nest_level);
48 : : static void ensure_tabstat_xact_level(PgStat_RelationStatus *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 : : * Determine the stats kind for a relation based on its relkind.
54 : : */
55 : : static inline PgStat_Kind
15 michael@paquier.xyz 56 :GNC 1303359 : pgstat_get_relation_kind(char relkind)
57 : : {
58 [ + + ]: 1303359 : if (relkind == RELKIND_INDEX)
59 : 695018 : return PGSTAT_KIND_INDEX;
60 : 608341 : return PGSTAT_KIND_RELATION;
61 : : }
62 : :
63 : :
64 : : /*
65 : : * Copy stats between relations. This is used for things like REINDEX
66 : : * CONCURRENTLY.
67 : : */
68 : : void
1604 andres@anarazel.de 69 :CBC 326 : pgstat_copy_relation_stats(Relation dst, Relation src)
70 : : {
15 michael@paquier.xyz 71 :GNC 326 : PgStat_Kind kind = pgstat_get_relation_kind(src->rd_rel->relkind);
72 : :
73 [ + - ]: 326 : if (kind == PGSTAT_KIND_INDEX)
74 : : {
75 : : PgStat_StatIdxEntry *srcstats;
76 : : PgStatShared_Index *dstshstats;
77 : : PgStat_EntryRef *dst_ref;
78 : :
79 : 326 : srcstats = pgstat_fetch_stat_idxentry_ext(src->rd_rel->relisshared,
80 : : RelationGetRelid(src),
81 : : NULL);
82 [ + + ]: 326 : if (!srcstats)
83 : 193 : return;
84 : :
85 : 133 : dst_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_INDEX,
86 : 133 : dst->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
87 [ - + ]: 133 : RelationGetRelid(dst),
88 : : false);
89 : :
90 : 133 : dstshstats = (PgStatShared_Index *) dst_ref->shared_stats;
91 : 133 : dstshstats->stats = *srcstats;
92 : :
93 : 133 : pgstat_unlock_entry(dst_ref);
94 : : }
95 : : else
96 : : {
97 : : PgStat_StatTabEntry *srcstats;
98 : : PgStatShared_Relation *dstshstats;
99 : : PgStat_EntryRef *dst_ref;
100 : :
15 michael@paquier.xyz 101 :UNC 0 : srcstats = pgstat_fetch_stat_tabentry_ext(src->rd_rel->relisshared,
102 : : RelationGetRelid(src),
103 : : NULL);
104 [ # # ]: 0 : if (!srcstats)
105 : 0 : return;
106 : :
107 : 0 : dst_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION,
108 : 0 : dst->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
109 [ # # ]: 0 : RelationGetRelid(dst),
110 : : false);
111 : :
112 : 0 : dstshstats = (PgStatShared_Relation *) dst_ref->shared_stats;
113 : 0 : dstshstats->stats = *srcstats;
114 : :
115 : 0 : pgstat_unlock_entry(dst_ref);
116 : : }
117 : : }
118 : :
119 : : /*
120 : : * Initialize a relcache entry to count access statistics. Called whenever a
121 : : * relation is opened.
122 : : *
123 : : * We assume that a relcache entry's pgstat_info field is zeroed by relcache.c
124 : : * when the relcache entry is made; thereafter it is long-lived data.
125 : : *
126 : : * This does not create a reference to a stats entry in shared memory, nor
127 : : * allocate memory for the pending stats. That happens in
128 : : * pgstat_assoc_relation().
129 : : */
130 : : void
1604 andres@anarazel.de 131 :CBC 25902102 : pgstat_init_relation(Relation rel)
132 : : {
1620 133 : 25902102 : char relkind = rel->rd_rel->relkind;
134 : :
135 : : /*
136 : : * We only count stats for relations with storage and partitioned tables
137 : : */
138 [ + + + + : 25902102 : if (!RELKIND_HAS_STORAGE(relkind) && relkind != RELKIND_PARTITIONED_TABLE)
+ + + + +
+ + + ]
139 : : {
1604 140 : 99326 : rel->pgstat_enabled = false;
1620 141 : 99326 : rel->pgstat_info = NULL;
142 : 99326 : return;
143 : : }
144 : :
1604 145 [ + + ]: 25802776 : if (!pgstat_track_counts)
146 : : {
147 [ + + ]: 197 : if (rel->pgstat_info)
148 : 12 : pgstat_unlink_relation(rel);
149 : :
150 : : /* We're not counting at all */
151 : 197 : rel->pgstat_enabled = false;
1620 152 : 197 : rel->pgstat_info = NULL;
153 : 197 : return;
154 : : }
155 : :
1604 156 : 25802579 : rel->pgstat_enabled = true;
157 : : }
158 : :
159 : : /*
160 : : * Prepare for statistics for this relation to be collected.
161 : : *
162 : : * This ensures we have a reference to the stats entry before stats can be
163 : : * generated. That is important because a relation drop in another connection
164 : : * could otherwise lead to the stats entry being dropped, which then later
165 : : * would get recreated when flushing stats.
166 : : *
167 : : * This is separate from pgstat_init_relation() as it is not uncommon for
168 : : * relcache entries to be opened without ever getting stats reported.
169 : : */
170 : : void
171 : 1166410 : pgstat_assoc_relation(Relation rel)
172 : : {
173 : : PgStat_Kind kind;
174 : :
175 [ - + ]: 1166410 : Assert(rel->pgstat_enabled);
176 [ - + ]: 1166410 : Assert(rel->pgstat_info == NULL);
177 : :
15 michael@paquier.xyz 178 :GNC 1166410 : kind = pgstat_get_relation_kind(rel->rd_rel->relkind);
179 : :
180 : : /* find or make the PgStat_RelationStatus entry, and update link */
181 : 2332820 : rel->pgstat_info = pgstat_prep_relation_pending(kind,
182 : : RelationGetRelid(rel),
1604 andres@anarazel.de 183 :CBC 1166410 : rel->rd_rel->relisshared);
184 : :
185 : : /* don't allow link a stats to multiple relcache entries */
186 [ - + ]: 1166410 : Assert(rel->pgstat_info->relation == NULL);
187 : :
188 : : /* mark this relation as the owner */
189 : 1166410 : rel->pgstat_info->relation = rel;
190 : 1166410 : }
191 : :
192 : : /*
193 : : * Break the mutual link between a relcache entry and pending stats entry.
194 : : * This must be called whenever one end of the link is removed.
195 : : */
196 : : void
197 : 1845250 : pgstat_unlink_relation(Relation rel)
198 : : {
199 : : /* remove the link to stats info if any */
200 [ + + ]: 1845250 : if (rel->pgstat_info == NULL)
201 : 678840 : return;
202 : :
203 : : /* link sanity check */
204 [ - + ]: 1166410 : Assert(rel->pgstat_info->relation == rel);
205 : 1166410 : rel->pgstat_info->relation = NULL;
206 : 1166410 : rel->pgstat_info = NULL;
207 : : }
208 : :
209 : : /*
210 : : * Ensure that stats are dropped if transaction aborts.
211 : : */
212 : : void
213 : 88038 : pgstat_create_relation(Relation rel)
214 : : {
15 michael@paquier.xyz 215 :GNC 88038 : PgStat_Kind kind = pgstat_get_relation_kind(rel->rd_rel->relkind);
216 : :
217 : 88038 : pgstat_create_transactional(kind,
1604 andres@anarazel.de 218 :CBC 88038 : rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
219 [ + + ]: 88038 : RelationGetRelid(rel));
220 : 88038 : }
221 : :
222 : : /*
223 : : * Ensure that stats are dropped if transaction commits.
224 : : */
225 : : void
226 : 48585 : pgstat_drop_relation(Relation rel)
227 : : {
228 : 48585 : int nest_level = GetCurrentTransactionNestLevel();
229 : : PgStat_RelationStatus *pgstat_info;
15 michael@paquier.xyz 230 :GNC 48585 : PgStat_Kind kind = pgstat_get_relation_kind(rel->rd_rel->relkind);
231 : :
232 : 48585 : pgstat_drop_transactional(kind,
1604 andres@anarazel.de 233 :CBC 48585 : rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId,
234 [ - + ]: 48585 : RelationGetRelid(rel));
235 : :
236 [ + + + + : 48585 : if (!pgstat_should_count_relation(rel))
+ + ]
237 : 5082 : return;
238 : :
239 : : /*
240 : : * Transactionally set counters to 0. That ensures that accesses to
241 : : * pg_stat_xact_all_tables inside the transaction show 0.
242 : : *
243 : : * Indexes have no transactional counters, so leave.
244 : : */
245 : 43503 : pgstat_info = rel->pgstat_info;
13 michael@paquier.xyz 246 [ + + ]:GNC 43503 : if (pgstat_info->kind == PGSTAT_KIND_INDEX)
247 : 14834 : return;
248 : :
249 [ + + ]: 28669 : if (pgstat_info->tab.trans &&
250 [ + + ]: 881 : pgstat_info->tab.trans->nest_level == nest_level)
251 : : {
252 : 877 : save_truncdrop_counters(pgstat_info->tab.trans, true);
253 : 877 : pgstat_info->tab.trans->tuples_inserted = 0;
254 : 877 : pgstat_info->tab.trans->tuples_updated = 0;
255 : 877 : pgstat_info->tab.trans->tuples_deleted = 0;
256 : : }
257 : : }
258 : :
259 : : /*
260 : : * Report that the table was just vacuumed and flush IO statistics.
261 : : */
262 : : void
253 michael@paquier.xyz 263 :CBC 16437 : pgstat_report_vacuum(Relation rel, PgStat_Counter livetuples,
264 : : PgStat_Counter deadtuples, TimestampTz starttime)
265 : : {
266 : : PgStat_EntryRef *entry_ref;
267 : : PgStatShared_Relation *shtabentry;
268 : : PgStat_StatTabEntry *tabentry;
269 [ + + ]: 16437 : Oid dboid = (rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId);
270 : : TimestampTz ts;
271 : : PgStat_Counter elapsedtime;
272 : :
1604 andres@anarazel.de 273 [ - + ]: 16437 : if (!pgstat_track_counts)
1620 andres@anarazel.de 274 :UBC 0 : return;
275 : :
276 : : /* Store the data in the table's hash table entry. */
1604 andres@anarazel.de 277 :CBC 16437 : ts = GetCurrentTimestamp();
576 michael@paquier.xyz 278 : 16437 : elapsedtime = TimestampDifferenceMilliseconds(starttime, ts);
279 : :
280 : : /* block acquiring lock for the same reason as pgstat_report_autovac() */
253 281 : 16437 : entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, dboid,
282 : 16437 : RelationGetRelid(rel), false);
283 : :
1604 andres@anarazel.de 284 : 16437 : shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats;
285 : 16437 : tabentry = &shtabentry->stats;
286 : :
1360 michael@paquier.xyz 287 : 16437 : tabentry->live_tuples = livetuples;
288 : 16437 : tabentry->dead_tuples = deadtuples;
289 : :
290 : : /*
291 : : * It is quite possible that a non-aggressive VACUUM ended up skipping
292 : : * various pages, however, we'll zero the insert counter here regardless.
293 : : * It's currently used only to track when we need to perform an "insert"
294 : : * autovacuum, which are mainly intended to freeze newly inserted tuples.
295 : : * Zeroing this may just mean we'll not try to vacuum the table again
296 : : * until enough tuples have been inserted to trigger another insert
297 : : * autovacuum. An anti-wraparound autovacuum will catch any persistent
298 : : * stragglers.
299 : : */
300 : 16437 : tabentry->ins_since_vacuum = 0;
301 : :
906 heikki.linnakangas@i 302 [ + + ]: 16437 : if (AmAutoVacuumWorkerProcess())
303 : : {
1360 michael@paquier.xyz 304 : 473 : tabentry->last_autovacuum_time = ts;
305 : 473 : tabentry->autovacuum_count++;
576 306 : 473 : tabentry->total_autovacuum_time += elapsedtime;
307 : : }
308 : : else
309 : : {
1360 310 : 15964 : tabentry->last_vacuum_time = ts;
1604 andres@anarazel.de 311 : 15964 : tabentry->vacuum_count++;
576 michael@paquier.xyz 312 : 15964 : tabentry->total_vacuum_time += elapsedtime;
313 : : }
314 : :
1604 andres@anarazel.de 315 : 16437 : pgstat_unlock_entry(entry_ref);
316 : :
317 : : /*
318 : : * Flush IO statistics now. pgstat_report_stat() will flush IO stats,
319 : : * however this will not be called until after an entire autovacuum cycle
320 : : * is done -- which will likely vacuum many relations -- or until the
321 : : * VACUUM command has processed all tables and committed.
322 : : */
1296 323 : 16437 : pgstat_flush_io(false);
583 michael@paquier.xyz 324 : 16437 : (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO);
325 : : }
326 : :
327 : : /*
328 : : * Report that the table was just analyzed and flush IO statistics.
329 : : *
330 : : * Caller must provide new live- and dead-tuples estimates, as well as a
331 : : * flag indicating whether to reset the mod_since_analyze counter.
332 : : */
333 : : void
1620 andres@anarazel.de 334 : 10704 : pgstat_report_analyze(Relation rel,
335 : : PgStat_Counter livetuples, PgStat_Counter deadtuples,
336 : : bool resetcounter, TimestampTz starttime)
337 : : {
338 : : PgStat_EntryRef *entry_ref;
339 : : PgStatShared_Relation *shtabentry;
340 : : PgStat_StatTabEntry *tabentry;
1604 341 [ + + ]: 10704 : Oid dboid = (rel->rd_rel->relisshared ? InvalidOid : MyDatabaseId);
342 : : TimestampTz ts;
343 : : PgStat_Counter elapsedtime;
344 : :
345 [ - + ]: 10704 : if (!pgstat_track_counts)
1620 andres@anarazel.de 346 :UBC 0 : return;
347 : :
348 : : /*
349 : : * Unlike VACUUM, ANALYZE might be running inside a transaction that has
350 : : * already inserted and/or deleted rows in the target table. ANALYZE will
351 : : * have counted such rows as live or dead respectively. Because we will
352 : : * report our counts of such rows at transaction end, we should subtract
353 : : * off these counts from the update we're making now, else they'll be
354 : : * double-counted after commit. (This approach also ensures that the
355 : : * shared stats entry ends up with the right numbers if we abort instead
356 : : * of committing.)
357 : : *
358 : : * Waste no time on partitioned tables, though.
359 : : */
1604 andres@anarazel.de 360 [ + + + + :CBC 10704 : if (pgstat_should_count_relation(rel) &&
+ + ]
1620 361 [ + + ]: 10658 : rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
362 : : {
363 : : PgStat_TableXactStatus *trans;
364 : :
13 michael@paquier.xyz 365 [ - + ]:GNC 10153 : Assert(rel->pgstat_info->kind == PGSTAT_KIND_RELATION);
366 [ + + ]: 10288 : for (trans = rel->pgstat_info->tab.trans; trans; trans = trans->upper)
367 : : {
1620 andres@anarazel.de 368 :CBC 135 : livetuples -= trans->tuples_inserted - trans->tuples_deleted;
369 : 135 : deadtuples -= trans->tuples_updated + trans->tuples_deleted;
370 : : }
371 : : /* count stuff inserted by already-aborted subxacts, too */
13 michael@paquier.xyz 372 :GNC 10153 : deadtuples -= rel->pgstat_info->tab.counts.delta_dead_tuples;
373 : : /* Since ANALYZE's counts are estimates, we could have underflowed */
1620 andres@anarazel.de 374 :CBC 10153 : livetuples = Max(livetuples, 0);
375 : 10153 : deadtuples = Max(deadtuples, 0);
376 : : }
377 : :
378 : : /* Store the data in the table's hash table entry. */
576 michael@paquier.xyz 379 : 10704 : ts = GetCurrentTimestamp();
380 : 10704 : elapsedtime = TimestampDifferenceMilliseconds(starttime, ts);
381 : :
382 : : /* block acquiring lock for the same reason as pgstat_report_autovac() */
1604 andres@anarazel.de 383 : 10704 : entry_ref = pgstat_get_entry_ref_locked(PGSTAT_KIND_RELATION, dboid,
384 : 10704 : RelationGetRelid(rel),
385 : : false);
386 : : /* can't get dropped while accessed */
387 [ + - - + ]: 10704 : Assert(entry_ref != NULL && entry_ref->shared_stats != NULL);
388 : :
389 : 10704 : shtabentry = (PgStatShared_Relation *) entry_ref->shared_stats;
390 : 10704 : tabentry = &shtabentry->stats;
391 : :
1360 michael@paquier.xyz 392 : 10704 : tabentry->live_tuples = livetuples;
393 : 10704 : tabentry->dead_tuples = deadtuples;
394 : :
395 : : /*
396 : : * If commanded, reset mod_since_analyze to zero. This forgets any
397 : : * changes that were committed while the ANALYZE was in progress, but we
398 : : * have no good way to estimate how many of those there were.
399 : : */
1604 andres@anarazel.de 400 [ + + ]: 10704 : if (resetcounter)
1360 michael@paquier.xyz 401 : 10664 : tabentry->mod_since_analyze = 0;
402 : :
906 heikki.linnakangas@i 403 [ + + ]: 10704 : if (AmAutoVacuumWorkerProcess())
404 : : {
576 michael@paquier.xyz 405 : 758 : tabentry->last_autoanalyze_time = ts;
1360 406 : 758 : tabentry->autoanalyze_count++;
576 407 : 758 : tabentry->total_autoanalyze_time += elapsedtime;
408 : : }
409 : : else
410 : : {
411 : 9946 : tabentry->last_analyze_time = ts;
1604 andres@anarazel.de 412 : 9946 : tabentry->analyze_count++;
576 michael@paquier.xyz 413 : 9946 : tabentry->total_analyze_time += elapsedtime;
414 : : }
415 : :
1604 andres@anarazel.de 416 : 10704 : pgstat_unlock_entry(entry_ref);
417 : :
418 : : /* see pgstat_report_vacuum() */
1296 419 : 10704 : pgstat_flush_io(false);
583 michael@paquier.xyz 420 : 10704 : (void) pgstat_flush_backend(false, PGSTAT_BACKEND_FLUSH_IO);
421 : : }
422 : :
423 : : /*
424 : : * count a tuple insertion of n tuples
425 : : */
426 : : void
1620 andres@anarazel.de 427 : 12311875 : pgstat_count_heap_insert(Relation rel, PgStat_Counter n)
428 : : {
1604 429 [ + + + + : 12311875 : if (pgstat_should_count_relation(rel))
+ + ]
430 : : {
13 michael@paquier.xyz 431 :GNC 12103698 : PgStat_RelationStatus *pgstat_info = rel->pgstat_info;
432 : :
433 [ - + ]: 12103698 : Assert(pgstat_info->kind == PGSTAT_KIND_RELATION);
434 : :
1620 andres@anarazel.de 435 :CBC 12103698 : ensure_tabstat_xact_level(pgstat_info);
13 michael@paquier.xyz 436 :GNC 12103698 : pgstat_info->tab.trans->tuples_inserted += n;
437 : : }
1620 andres@anarazel.de 438 :CBC 12311875 : }
439 : :
440 : : /*
441 : : * count a tuple update
442 : : */
443 : : void
1253 pg@bowt.ie 444 : 2382504 : pgstat_count_heap_update(Relation rel, bool hot, bool newpage)
445 : : {
446 [ + + - + ]: 2382504 : Assert(!(hot && newpage));
447 : :
1604 andres@anarazel.de 448 [ + + - + : 2382504 : if (pgstat_should_count_relation(rel))
+ + ]
449 : : {
13 michael@paquier.xyz 450 :GNC 2382502 : PgStat_RelationStatus *pgstat_info = rel->pgstat_info;
451 : :
452 [ - + ]: 2382502 : Assert(pgstat_info->kind == PGSTAT_KIND_RELATION);
453 : :
1620 andres@anarazel.de 454 :CBC 2382502 : ensure_tabstat_xact_level(pgstat_info);
13 michael@paquier.xyz 455 :GNC 2382502 : pgstat_info->tab.trans->tuples_updated++;
456 : :
457 : : /*
458 : : * tuples_hot_updated and tuples_newpage_updated counters are
459 : : * nontransactional, so just advance them
460 : : */
1620 andres@anarazel.de 461 [ + + ]:CBC 2382502 : if (hot)
13 michael@paquier.xyz 462 :GNC 167139 : pgstat_info->tab.counts.tuples_hot_updated++;
1253 pg@bowt.ie 463 [ + + ]:CBC 2215363 : else if (newpage)
13 michael@paquier.xyz 464 :GNC 2198994 : pgstat_info->tab.counts.tuples_newpage_updated++;
465 : : }
1620 andres@anarazel.de 466 :CBC 2382504 : }
467 : :
468 : : /*
469 : : * count a tuple deletion
470 : : */
471 : : void
472 : 1860794 : pgstat_count_heap_delete(Relation rel)
473 : : {
1604 474 [ - + - - : 1860794 : if (pgstat_should_count_relation(rel))
+ - ]
475 : : {
13 michael@paquier.xyz 476 :GNC 1860794 : PgStat_RelationStatus *pgstat_info = rel->pgstat_info;
477 : :
478 [ - + ]: 1860794 : Assert(pgstat_info->kind == PGSTAT_KIND_RELATION);
479 : :
1620 andres@anarazel.de 480 :CBC 1860794 : ensure_tabstat_xact_level(pgstat_info);
13 michael@paquier.xyz 481 :GNC 1860794 : pgstat_info->tab.trans->tuples_deleted++;
482 : : }
1620 andres@anarazel.de 483 :CBC 1860794 : }
484 : :
485 : : /*
486 : : * update tuple counters due to truncate
487 : : */
488 : : void
489 : 2347 : pgstat_count_truncate(Relation rel)
490 : : {
1604 491 [ + + + - : 2347 : if (pgstat_should_count_relation(rel))
+ - ]
492 : : {
13 michael@paquier.xyz 493 :GNC 2347 : PgStat_RelationStatus *pgstat_info = rel->pgstat_info;
494 : :
495 [ - + ]: 2347 : Assert(pgstat_info->kind == PGSTAT_KIND_RELATION);
496 : :
1620 andres@anarazel.de 497 :CBC 2347 : ensure_tabstat_xact_level(pgstat_info);
13 michael@paquier.xyz 498 :GNC 2347 : save_truncdrop_counters(pgstat_info->tab.trans, false);
499 : 2347 : pgstat_info->tab.trans->tuples_inserted = 0;
500 : 2347 : pgstat_info->tab.trans->tuples_updated = 0;
501 : 2347 : pgstat_info->tab.trans->tuples_deleted = 0;
502 : : }
1620 andres@anarazel.de 503 :CBC 2347 : }
504 : :
505 : : /*
506 : : * update dead-tuples count
507 : : *
508 : : * The semantics of this are that we are reporting the nontransactional
509 : : * recovery of "delta" dead tuples; so delta_dead_tuples decreases
510 : : * rather than increasing, and the change goes straight into the per-table
511 : : * counter, not into transactional state.
512 : : */
513 : : void
514 : 21461 : pgstat_update_heap_dead_tuples(Relation rel, int delta)
515 : : {
1604 516 [ - + - - : 21461 : if (pgstat_should_count_relation(rel))
+ - ]
517 : : {
13 michael@paquier.xyz 518 :GNC 21461 : PgStat_RelationStatus *pgstat_info = rel->pgstat_info;
519 : :
520 [ - + ]: 21461 : Assert(pgstat_info->kind == PGSTAT_KIND_RELATION);
521 : :
522 : 21461 : pgstat_info->tab.counts.delta_dead_tuples -= delta;
523 : : }
1620 andres@anarazel.de 524 :CBC 21461 : }
525 : :
526 : : /*
527 : : * Support function for the SQL-callable pgstat* functions. Returns
528 : : * the collected statistics for one table or NULL. NULL doesn't mean
529 : : * that the table doesn't exist, just that there are no statistics, so the
530 : : * caller is better off to report ZERO instead.
531 : : */
532 : : PgStat_StatTabEntry *
1604 533 : 6576 : pgstat_fetch_stat_tabentry(Oid relid)
534 : : {
140 nathan@postgresql.or 535 : 6576 : return pgstat_fetch_stat_tabentry_ext(IsSharedRelation(relid), relid, NULL);
536 : : }
537 : :
538 : : /*
539 : : * More efficient version of pgstat_fetch_stat_tabentry(), allowing to specify
540 : : * whether the to-be-accessed table is a shared relation or not. This version
541 : : * also returns whether the caller can pfree() the result if desired.
542 : : */
543 : : PgStat_StatTabEntry *
544 : 38107 : pgstat_fetch_stat_tabentry_ext(bool shared, Oid reloid, bool *may_free)
545 : : {
1604 andres@anarazel.de 546 [ + + ]: 38107 : Oid dboid = (shared ? InvalidOid : MyDatabaseId);
547 : :
548 : 38107 : return (PgStat_StatTabEntry *)
140 nathan@postgresql.or 549 : 38107 : pgstat_fetch_entry(PGSTAT_KIND_RELATION, dboid, reloid, may_free);
550 : : }
551 : :
552 : : /*
553 : : * find any existing PgStat_RelationStatus entry for rel and kind
554 : : *
555 : : * Find any existing PgStat_RelationStatus entry for rel_id in the current
556 : : * database. If not found, try finding from shared tables.
557 : : *
558 : : * If an entry is found, copy it. For a PGSTAT_KIND_INDEX entry, just return
559 : : * the copy. For a PGSTAT_KIND_RELATION entry, increment the copy's counters
560 : : * with their subtransaction counterparts, then return the copy. The caller
561 : : * may need to pfree() the copy.
562 : : *
563 : : * If no entry found, return NULL, don't create a new one.
564 : : */
565 : : PgStat_RelationStatus *
13 michael@paquier.xyz 566 :GNC 32 : find_relstat_entry_kind(PgStat_Kind kind, Oid rel_id)
567 : : {
568 : : PgStat_EntryRef *entry_ref;
569 : : PgStat_TableXactStatus *trans;
570 : 32 : PgStat_RelationStatus *relentry = NULL;
571 : 32 : PgStat_RelationStatus *relstatus = NULL;
572 : :
15 573 : 32 : entry_ref = pgstat_fetch_pending_entry(kind, MyDatabaseId, rel_id);
1604 andres@anarazel.de 574 [ + + ]:CBC 32 : if (!entry_ref)
575 : : {
15 michael@paquier.xyz 576 :GNC 8 : entry_ref = pgstat_fetch_pending_entry(kind, InvalidOid, rel_id);
1032 michael@paquier.xyz 577 [ + - ]:CBC 8 : if (!entry_ref)
13 michael@paquier.xyz 578 :GNC 8 : return relstatus;
579 : : }
580 : :
581 : 24 : relentry = (PgStat_RelationStatus *) entry_ref->pending;
582 : 24 : relstatus = palloc_object(PgStat_RelationStatus);
583 : 24 : *relstatus = *relentry;
584 : :
585 : : /*
586 : : * For index entries, just return the copy. There is no transactional
587 : : * data.
588 : : */
589 [ - + ]: 24 : if (kind == PGSTAT_KIND_INDEX)
13 michael@paquier.xyz 590 :UNC 0 : return relstatus;
591 : :
592 : : /*
593 : : * Reset relstatus->tab.trans in the copy of PgStat_RelationStatus as it
594 : : * may point to a shared memory area. Its data is saved below, so
595 : : * removing it does not matter.
596 : : */
13 michael@paquier.xyz 597 :GNC 24 : relstatus->tab.trans = NULL;
598 : :
599 : : /*
600 : : * Live subtransaction counts are not included yet. This is not a hot
601 : : * code path so reconcile tuples_inserted, tuples_updated and
602 : : * tuples_deleted even if the caller may not be interested in this data.
603 : : */
604 [ + + ]: 56 : for (trans = relentry->tab.trans; trans != NULL; trans = trans->upper)
605 : : {
606 : 32 : relstatus->tab.counts.tuples_inserted += trans->tuples_inserted;
607 : 32 : relstatus->tab.counts.tuples_updated += trans->tuples_updated;
608 : 32 : relstatus->tab.counts.tuples_deleted += trans->tuples_deleted;
609 : : }
610 : :
611 : 24 : return relstatus;
612 : : }
613 : :
614 : : /*
615 : : * Perform relation stats specific end-of-transaction work. Helper for
616 : : * AtEOXact_PgStat.
617 : : *
618 : : * Transfer transactional insert/update counts into the base relstat entries.
619 : : * We don't bother to free any of the transactional state, since it's all in
620 : : * TopTransactionContext and will go away anyway.
621 : : */
622 : : void
1620 andres@anarazel.de 623 :CBC 157283 : AtEOXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit)
624 : : {
625 : : PgStat_TableXactStatus *trans;
626 : :
627 [ + + ]: 607529 : for (trans = xact_state->first; trans != NULL; trans = trans->next)
628 : : {
629 : : PgStat_RelationStatus *relstat;
630 : :
631 [ - + ]: 450246 : Assert(trans->nest_level == 1);
632 [ - + ]: 450246 : Assert(trans->upper == NULL);
13 michael@paquier.xyz 633 :GNC 450246 : relstat = trans->parent;
634 [ - + ]: 450246 : Assert(relstat->tab.trans == trans);
635 : : /* restore pre-truncate/drop stats (if any) in case of aborted xact */
1620 andres@anarazel.de 636 [ + + ]:CBC 450246 : if (!isCommit)
1604 637 : 16615 : restore_truncdrop_counters(trans);
638 : : /* count attempted actions regardless of commit/abort */
13 michael@paquier.xyz 639 :GNC 450246 : relstat->tab.counts.tuples_inserted += trans->tuples_inserted;
640 : 450246 : relstat->tab.counts.tuples_updated += trans->tuples_updated;
641 : 450246 : relstat->tab.counts.tuples_deleted += trans->tuples_deleted;
1620 andres@anarazel.de 642 [ + + ]:CBC 450246 : if (isCommit)
643 : : {
13 michael@paquier.xyz 644 :GNC 433631 : relstat->tab.counts.truncdropped = trans->truncdropped;
1620 andres@anarazel.de 645 [ + + ]:CBC 433631 : if (trans->truncdropped)
646 : : {
647 : : /* forget live/dead stats seen by backend thus far */
13 michael@paquier.xyz 648 :GNC 2943 : relstat->tab.counts.delta_live_tuples = 0;
649 : 2943 : relstat->tab.counts.delta_dead_tuples = 0;
650 : : }
651 : : /* insert adds a live tuple, delete removes one */
652 : 433631 : relstat->tab.counts.delta_live_tuples +=
1620 andres@anarazel.de 653 :CBC 433631 : trans->tuples_inserted - trans->tuples_deleted;
654 : : /* update and delete each create a dead tuple */
13 michael@paquier.xyz 655 :GNC 433631 : relstat->tab.counts.delta_dead_tuples +=
1620 andres@anarazel.de 656 :CBC 433631 : trans->tuples_updated + trans->tuples_deleted;
657 : : /* insert, update, delete each count as one change event */
13 michael@paquier.xyz 658 :GNC 433631 : relstat->tab.counts.changed_tuples +=
1620 andres@anarazel.de 659 :CBC 433631 : trans->tuples_inserted + trans->tuples_updated +
660 : 433631 : trans->tuples_deleted;
661 : : }
662 : : else
663 : : {
664 : : /* inserted tuples are dead, deleted tuples are unaffected */
13 michael@paquier.xyz 665 :GNC 16615 : relstat->tab.counts.delta_dead_tuples +=
1620 andres@anarazel.de 666 :CBC 16615 : trans->tuples_inserted + trans->tuples_updated;
667 : : /* an aborted xact generates no changed_tuple events */
668 : : }
13 michael@paquier.xyz 669 :GNC 450246 : relstat->tab.trans = NULL;
670 : : }
1620 andres@anarazel.de 671 :CBC 157283 : }
672 : :
673 : : /*
674 : : * Perform relation stats specific end-of-sub-transaction work. Helper for
675 : : * AtEOSubXact_PgStat.
676 : : *
677 : : * Transfer transactional insert/update counts into the next higher
678 : : * subtransaction state.
679 : : */
680 : : void
681 : 16197 : AtEOSubXact_PgStat_Relations(PgStat_SubXactStatus *xact_state, bool isCommit, int nestDepth)
682 : : {
683 : : PgStat_TableXactStatus *trans;
684 : : PgStat_TableXactStatus *next_trans;
685 : :
686 [ + + ]: 32872 : for (trans = xact_state->first; trans != NULL; trans = next_trans)
687 : : {
688 : : PgStat_RelationStatus *relstat;
689 : :
690 : 16675 : next_trans = trans->next;
691 [ - + ]: 16675 : Assert(trans->nest_level == nestDepth);
13 michael@paquier.xyz 692 :GNC 16675 : relstat = trans->parent;
693 [ - + ]: 16675 : Assert(relstat->tab.trans == trans);
694 : :
1620 andres@anarazel.de 695 [ + + ]:CBC 16675 : if (isCommit)
696 : : {
697 [ + + + + ]: 15620 : if (trans->upper && trans->upper->nest_level == nestDepth - 1)
698 : : {
699 [ + + ]: 14408 : if (trans->truncdropped)
700 : : {
701 : : /* propagate the truncate/drop status one level up */
1604 702 : 16 : save_truncdrop_counters(trans->upper, false);
703 : : /* replace upper xact stats with ours */
1620 704 : 16 : trans->upper->tuples_inserted = trans->tuples_inserted;
705 : 16 : trans->upper->tuples_updated = trans->tuples_updated;
706 : 16 : trans->upper->tuples_deleted = trans->tuples_deleted;
707 : : }
708 : : else
709 : : {
710 : 14392 : trans->upper->tuples_inserted += trans->tuples_inserted;
711 : 14392 : trans->upper->tuples_updated += trans->tuples_updated;
712 : 14392 : trans->upper->tuples_deleted += trans->tuples_deleted;
713 : : }
13 michael@paquier.xyz 714 :GNC 14408 : relstat->tab.trans = trans->upper;
1620 andres@anarazel.de 715 :CBC 14408 : pfree(trans);
716 : : }
717 : : else
718 : : {
719 : : /*
720 : : * When there isn't an immediate parent state, we can just
721 : : * reuse the record instead of going through a palloc/pfree
722 : : * pushup (this works since it's all in TopTransactionContext
723 : : * anyway). We have to re-link it into the parent level,
724 : : * though, and that might mean pushing a new entry into the
725 : : * pgStatXactStack.
726 : : */
727 : : PgStat_SubXactStatus *upper_xact_state;
728 : :
1604 729 : 1212 : upper_xact_state = pgstat_get_xact_stack_level(nestDepth - 1);
1620 730 : 1212 : trans->next = upper_xact_state->first;
731 : 1212 : upper_xact_state->first = trans;
732 : 1212 : trans->nest_level = nestDepth - 1;
733 : : }
734 : : }
735 : : else
736 : : {
737 : : /*
738 : : * On abort, update top-level relstat counts, then forget the
739 : : * subtransaction
740 : : */
741 : :
742 : : /* first restore values obliterated by truncate/drop */
1604 743 : 1055 : restore_truncdrop_counters(trans);
744 : : /* count attempted actions regardless of commit/abort */
13 michael@paquier.xyz 745 :GNC 1055 : relstat->tab.counts.tuples_inserted += trans->tuples_inserted;
746 : 1055 : relstat->tab.counts.tuples_updated += trans->tuples_updated;
747 : 1055 : relstat->tab.counts.tuples_deleted += trans->tuples_deleted;
748 : : /* inserted tuples are dead, deleted tuples are unaffected */
749 : 1055 : relstat->tab.counts.delta_dead_tuples +=
1620 andres@anarazel.de 750 :CBC 1055 : trans->tuples_inserted + trans->tuples_updated;
13 michael@paquier.xyz 751 :GNC 1055 : relstat->tab.trans = trans->upper;
1620 andres@anarazel.de 752 :CBC 1055 : pfree(trans);
753 : : }
754 : : }
755 : 16197 : }
756 : :
757 : : /*
758 : : * Generate 2PC records for all the pending transaction-dependent relation
759 : : * stats.
760 : : */
761 : : void
762 : 318 : AtPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
763 : : {
764 : : PgStat_TableXactStatus *trans;
765 : :
766 [ + + ]: 768 : for (trans = xact_state->first; trans != NULL; trans = trans->next)
767 : : {
768 : : PgStat_RelationStatus *relstat PG_USED_FOR_ASSERTS_ONLY;
769 : : TwoPhasePgStatRecord record;
770 : :
771 [ - + ]: 450 : Assert(trans->nest_level == 1);
772 [ - + ]: 450 : Assert(trans->upper == NULL);
13 michael@paquier.xyz 773 :GNC 450 : relstat = trans->parent;
774 [ - + ]: 450 : Assert(relstat->tab.trans == trans);
775 : :
1620 andres@anarazel.de 776 :CBC 450 : record.tuples_inserted = trans->tuples_inserted;
777 : 450 : record.tuples_updated = trans->tuples_updated;
778 : 450 : record.tuples_deleted = trans->tuples_deleted;
779 : 450 : record.inserted_pre_truncdrop = trans->inserted_pre_truncdrop;
780 : 450 : record.updated_pre_truncdrop = trans->updated_pre_truncdrop;
781 : 450 : record.deleted_pre_truncdrop = trans->deleted_pre_truncdrop;
13 michael@paquier.xyz 782 :GNC 450 : record.id = relstat->tab.id;
783 : 450 : record.shared = relstat->tab.shared;
1252 michael@paquier.xyz 784 :CBC 450 : record.truncdropped = trans->truncdropped;
785 : :
1620 andres@anarazel.de 786 : 450 : RegisterTwoPhaseRecord(TWOPHASE_RM_PGSTAT_ID, 0,
787 : : &record, sizeof(TwoPhasePgStatRecord));
788 : : }
789 : 318 : }
790 : :
791 : : /*
792 : : * All we need do here is unlink the transaction stats state from the
793 : : * nontransactional state. The nontransactional action counts will be
794 : : * reported to the stats system immediately, while the effects on live and
795 : : * dead tuple counts are preserved in the 2PC state file.
796 : : *
797 : : * Note: AtEOXact_PgStat_Relations is not called during PREPARE.
798 : : */
799 : : void
800 : 318 : PostPrepare_PgStat_Relations(PgStat_SubXactStatus *xact_state)
801 : : {
802 : : PgStat_TableXactStatus *trans;
803 : :
804 [ + + ]: 768 : for (trans = xact_state->first; trans != NULL; trans = trans->next)
805 : : {
806 : : PgStat_RelationStatus *relstat;
807 : :
13 michael@paquier.xyz 808 :GNC 450 : relstat = trans->parent;
809 : 450 : relstat->tab.trans = NULL;
810 : : }
1620 andres@anarazel.de 811 :CBC 318 : }
812 : :
813 : : /*
814 : : * 2PC processing routine for COMMIT PREPARED case.
815 : : *
816 : : * Load the saved counts into our local pgstats state.
817 : : */
818 : : void
416 michael@paquier.xyz 819 : 376 : pgstat_twophase_postcommit(FullTransactionId fxid, uint16 info,
820 : : void *recdata, uint32 len)
821 : : {
1620 andres@anarazel.de 822 : 376 : TwoPhasePgStatRecord *rec = (TwoPhasePgStatRecord *) recdata;
823 : : PgStat_RelationStatus *pgstat_info;
824 : :
825 : : /* Find or create a relstat entry for the rel */
15 michael@paquier.xyz 826 :GNC 376 : pgstat_info = pgstat_prep_relation_pending(PGSTAT_KIND_RELATION, rec->id, rec->shared);
827 : :
828 : : /* Same math as in AtEOXact_PgStat, commit case */
13 829 : 376 : pgstat_info->tab.counts.tuples_inserted += rec->tuples_inserted;
830 : 376 : pgstat_info->tab.counts.tuples_updated += rec->tuples_updated;
831 : 376 : pgstat_info->tab.counts.tuples_deleted += rec->tuples_deleted;
832 : 376 : pgstat_info->tab.counts.truncdropped = rec->truncdropped;
1252 michael@paquier.xyz 833 [ + + ]:CBC 376 : if (rec->truncdropped)
834 : : {
835 : : /* forget live/dead stats seen by backend thus far */
13 michael@paquier.xyz 836 :GNC 12 : pgstat_info->tab.counts.delta_live_tuples = 0;
837 : 12 : pgstat_info->tab.counts.delta_dead_tuples = 0;
838 : : }
839 : 376 : pgstat_info->tab.counts.delta_live_tuples +=
1620 andres@anarazel.de 840 :CBC 376 : rec->tuples_inserted - rec->tuples_deleted;
13 michael@paquier.xyz 841 :GNC 376 : pgstat_info->tab.counts.delta_dead_tuples +=
1620 andres@anarazel.de 842 :CBC 376 : rec->tuples_updated + rec->tuples_deleted;
13 michael@paquier.xyz 843 :GNC 376 : pgstat_info->tab.counts.changed_tuples +=
1620 andres@anarazel.de 844 :CBC 376 : rec->tuples_inserted + rec->tuples_updated +
845 : 376 : rec->tuples_deleted;
846 : 376 : }
847 : :
848 : : /*
849 : : * 2PC processing routine for ROLLBACK PREPARED case.
850 : : *
851 : : * Load the saved counts into our local pgstats state, but treat them
852 : : * as aborted.
853 : : */
854 : : void
416 michael@paquier.xyz 855 : 85 : pgstat_twophase_postabort(FullTransactionId fxid, uint16 info,
856 : : void *recdata, uint32 len)
857 : : {
1620 andres@anarazel.de 858 : 85 : TwoPhasePgStatRecord *rec = (TwoPhasePgStatRecord *) recdata;
859 : : PgStat_RelationStatus *pgstat_info;
860 : :
861 : : /* Find or create a relstat entry for the rel */
15 michael@paquier.xyz 862 :GNC 85 : pgstat_info = pgstat_prep_relation_pending(PGSTAT_KIND_RELATION, rec->id, rec->shared);
863 : :
864 : : /* Same math as in AtEOXact_PgStat, abort case */
1252 michael@paquier.xyz 865 [ + + ]:CBC 85 : if (rec->truncdropped)
866 : : {
1620 andres@anarazel.de 867 : 8 : rec->tuples_inserted = rec->inserted_pre_truncdrop;
868 : 8 : rec->tuples_updated = rec->updated_pre_truncdrop;
869 : 8 : rec->tuples_deleted = rec->deleted_pre_truncdrop;
870 : : }
13 michael@paquier.xyz 871 :GNC 85 : pgstat_info->tab.counts.tuples_inserted += rec->tuples_inserted;
872 : 85 : pgstat_info->tab.counts.tuples_updated += rec->tuples_updated;
873 : 85 : pgstat_info->tab.counts.tuples_deleted += rec->tuples_deleted;
874 : 85 : pgstat_info->tab.counts.delta_dead_tuples +=
1620 andres@anarazel.de 875 :CBC 85 : rec->tuples_inserted + rec->tuples_updated;
876 : 85 : }
877 : :
878 : : /*
879 : : * Flush out pending stats for the entry
880 : : *
881 : : * If nowait is true and the lock could not be immediately acquired, returns
882 : : * false without flushing the entry. Otherwise returns true.
883 : : *
884 : : * Some of the stats are copied to the corresponding pending database stats
885 : : * entry when successfully flushing.
886 : : */
887 : : bool
1604 888 : 444116 : pgstat_relation_flush_cb(PgStat_EntryRef *entry_ref, bool nowait)
889 : : {
890 : : Oid dboid;
891 : : PgStat_RelationStatus *lstats; /* pending stats entry */
892 : : PgStatShared_Relation *shtabstats;
893 : : PgStat_StatTabEntry *tabentry; /* table entry of shared stats */
894 : : PgStat_StatDBEntry *dbentry; /* pending database entry */
895 : :
896 : 444116 : dboid = entry_ref->shared_entry->key.dboid;
13 michael@paquier.xyz 897 :GNC 444116 : lstats = (PgStat_RelationStatus *) entry_ref->pending;
1604 andres@anarazel.de 898 :CBC 444116 : shtabstats = (PgStatShared_Relation *) entry_ref->shared_stats;
899 : :
900 : : /* ignore entries that didn't accumulate any actual counts */
13 michael@paquier.xyz 901 [ + + ]:GNC 444116 : if (pg_memory_is_all_zeros(&lstats->tab.counts,
902 : : sizeof(struct PgStat_TableCounts)))
1604 andres@anarazel.de 903 :CBC 3642 : return true;
904 : :
905 [ + + ]: 440474 : if (!pgstat_lock_entry(entry_ref, nowait))
906 : 2 : return false;
907 : :
908 : : /* add the values to the shared entry. */
909 : 440472 : tabentry = &shtabstats->stats;
910 : :
13 michael@paquier.xyz 911 :GNC 440472 : tabentry->numscans += lstats->tab.counts.numscans;
912 [ + + ]: 440472 : if (lstats->tab.counts.numscans)
913 : : {
1413 andres@anarazel.de 914 :CBC 103383 : TimestampTz t = GetCurrentTransactionStopTimestamp();
915 : :
916 [ + + ]: 103383 : if (t > tabentry->lastscan)
917 : 102633 : tabentry->lastscan = t;
918 : : }
13 michael@paquier.xyz 919 :GNC 440472 : tabentry->tuples_returned += lstats->tab.counts.tuples_returned;
920 : 440472 : tabentry->tuples_fetched += lstats->tab.counts.tuples_fetched;
921 : 440472 : tabentry->tuples_inserted += lstats->tab.counts.tuples_inserted;
922 : 440472 : tabentry->tuples_updated += lstats->tab.counts.tuples_updated;
923 : 440472 : tabentry->tuples_deleted += lstats->tab.counts.tuples_deleted;
924 : 440472 : tabentry->tuples_hot_updated += lstats->tab.counts.tuples_hot_updated;
925 : 440472 : tabentry->tuples_newpage_updated += lstats->tab.counts.tuples_newpage_updated;
926 : :
927 : : /*
928 : : * If table was truncated/dropped, first reset the live/dead counters.
929 : : */
930 [ + + ]: 440472 : if (lstats->tab.counts.truncdropped)
931 : : {
1360 michael@paquier.xyz 932 :CBC 601 : tabentry->live_tuples = 0;
933 : 601 : tabentry->dead_tuples = 0;
934 : 601 : tabentry->ins_since_vacuum = 0;
935 : : }
936 : :
13 michael@paquier.xyz 937 :GNC 440472 : tabentry->live_tuples += lstats->tab.counts.delta_live_tuples;
938 : 440472 : tabentry->dead_tuples += lstats->tab.counts.delta_dead_tuples;
939 : 440472 : tabentry->mod_since_analyze += lstats->tab.counts.changed_tuples;
940 : :
941 : : /*
942 : : * Using tuples_inserted to update ins_since_vacuum does mean that we'll
943 : : * track aborted inserts too. This isn't ideal, but otherwise probably
944 : : * not worth adding an extra field for. It may just amount to autovacuums
945 : : * triggering for inserts more often than they maybe should, which is
946 : : * probably not going to be common enough to be too concerned about here.
947 : : */
948 : 440472 : tabentry->ins_since_vacuum += lstats->tab.counts.tuples_inserted;
949 : :
950 : 440472 : tabentry->blocks_fetched += lstats->tab.counts.blocks_fetched;
951 : 440472 : tabentry->blocks_hit += lstats->tab.counts.blocks_hit;
952 : :
953 : : /* Clamp live_tuples in case of negative delta_live_tuples */
1360 michael@paquier.xyz 954 :CBC 440472 : tabentry->live_tuples = Max(tabentry->live_tuples, 0);
955 : : /* Likewise for dead_tuples */
956 : 440472 : tabentry->dead_tuples = Max(tabentry->dead_tuples, 0);
957 : :
1604 andres@anarazel.de 958 : 440472 : pgstat_unlock_entry(entry_ref);
959 : :
960 : : /* The entry was successfully flushed, add the same to database stats */
961 : 440472 : dbentry = pgstat_prep_database_pending(dboid);
13 michael@paquier.xyz 962 :GNC 440472 : dbentry->tuples_returned += lstats->tab.counts.tuples_returned;
963 : 440472 : dbentry->tuples_fetched += lstats->tab.counts.tuples_fetched;
964 : 440472 : dbentry->tuples_inserted += lstats->tab.counts.tuples_inserted;
965 : 440472 : dbentry->tuples_updated += lstats->tab.counts.tuples_updated;
966 : 440472 : dbentry->tuples_deleted += lstats->tab.counts.tuples_deleted;
967 : 440472 : dbentry->blocks_fetched += lstats->tab.counts.blocks_fetched;
968 : 440472 : dbentry->blocks_hit += lstats->tab.counts.blocks_hit;
969 : :
1604 andres@anarazel.de 970 :CBC 440472 : return true;
971 : : }
972 : :
973 : : void
974 : 473519 : pgstat_relation_delete_pending_cb(PgStat_EntryRef *entry_ref)
975 : : {
13 michael@paquier.xyz 976 :GNC 473519 : PgStat_RelationStatus *pending = (PgStat_RelationStatus *) entry_ref->pending;
977 : :
1604 andres@anarazel.de 978 [ + + ]:CBC 473519 : if (pending->relation)
979 : 420292 : pgstat_unlink_relation(pending->relation);
1620 980 : 473519 : }
981 : :
982 : : void
325 michael@paquier.xyz 983 : 8092 : pgstat_relation_reset_timestamp_cb(PgStatShared_Common *header, TimestampTz ts)
984 : : {
985 : 8092 : ((PgStatShared_Relation *) header)->stats.stat_reset_time = ts;
986 : 8092 : }
987 : :
988 : : /*
989 : : * Find or create a PgStat_RelationStatus entry for rel. New entry is created and
990 : : * initialized if not exists.
991 : : */
992 : : static PgStat_RelationStatus *
15 michael@paquier.xyz 993 :GNC 1166871 : pgstat_prep_relation_pending(PgStat_Kind kind, Oid rel_id, bool isshared)
994 : : {
995 : : PgStat_EntryRef *entry_ref;
996 : : PgStat_RelationStatus *pending;
997 : :
998 [ + + ]: 1166871 : entry_ref = pgstat_prep_pending_entry(kind,
999 : : isshared ? InvalidOid : MyDatabaseId,
1000 : : rel_id, NULL);
1604 andres@anarazel.de 1001 :CBC 1166871 : pending = entry_ref->pending;
13 michael@paquier.xyz 1002 :GNC 1166871 : pending->kind = kind;
1003 [ + + ]: 1166871 : if (kind != PGSTAT_KIND_INDEX)
1004 : : {
1005 : 516241 : pending->tab.id = rel_id;
1006 : 516241 : pending->tab.shared = isshared;
1007 : : }
1008 : :
1604 andres@anarazel.de 1009 :CBC 1166871 : return pending;
1010 : : }
1011 : :
1012 : : /*
1013 : : * add a new (sub)transaction state record
1014 : : */
1015 : : static void
13 michael@paquier.xyz 1016 :GNC 466159 : add_tabstat_xact_level(PgStat_RelationStatus *pgstat_info, int nest_level)
1017 : : {
1018 : : PgStat_SubXactStatus *xact_state;
1019 : : PgStat_TableXactStatus *trans;
1020 : :
1021 : : /*
1022 : : * If this is the first rel to be modified at the current nest level, we
1023 : : * first have to push a transaction stack entry.
1024 : : */
1604 andres@anarazel.de 1025 :CBC 466159 : xact_state = pgstat_get_xact_stack_level(nest_level);
1026 : :
1027 : : /* Now make a per-table stack entry */
1028 : : trans = (PgStat_TableXactStatus *)
1620 1029 : 466159 : MemoryContextAllocZero(TopTransactionContext,
1030 : : sizeof(PgStat_TableXactStatus));
1031 : 466159 : trans->nest_level = nest_level;
13 michael@paquier.xyz 1032 :GNC 466159 : trans->upper = pgstat_info->tab.trans;
1620 andres@anarazel.de 1033 :CBC 466159 : trans->parent = pgstat_info;
1034 : 466159 : trans->next = xact_state->first;
1035 : 466159 : xact_state->first = trans;
13 michael@paquier.xyz 1036 :GNC 466159 : pgstat_info->tab.trans = trans;
1620 andres@anarazel.de 1037 :CBC 466159 : }
1038 : :
1039 : : /*
1040 : : * Add a new (sub)transaction record if needed.
1041 : : */
1042 : : static void
13 michael@paquier.xyz 1043 :GNC 16349341 : ensure_tabstat_xact_level(PgStat_RelationStatus *pgstat_info)
1044 : : {
1620 andres@anarazel.de 1045 :CBC 16349341 : int nest_level = GetCurrentTransactionNestLevel();
1046 : :
13 michael@paquier.xyz 1047 [ + + ]:GNC 16349341 : if (pgstat_info->tab.trans == NULL ||
1048 [ + + ]: 15898375 : pgstat_info->tab.trans->nest_level != nest_level)
1620 andres@anarazel.de 1049 :CBC 466159 : add_tabstat_xact_level(pgstat_info, nest_level);
1050 : 16349341 : }
1051 : :
1052 : : /*
1053 : : * Whenever a table is truncated/dropped, we save its i/u/d counters so that
1054 : : * they can be cleared, and if the (sub)xact that executed the truncate/drop
1055 : : * later aborts, the counters can be restored to the saved (pre-truncate/drop)
1056 : : * values.
1057 : : *
1058 : : * Note that for truncate we do this on the first truncate in any particular
1059 : : * subxact level only.
1060 : : */
1061 : : static void
1604 1062 : 3240 : save_truncdrop_counters(PgStat_TableXactStatus *trans, bool is_drop)
1063 : : {
1620 1064 [ + + + + ]: 3240 : if (!trans->truncdropped || is_drop)
1065 : : {
1066 : 3208 : trans->inserted_pre_truncdrop = trans->tuples_inserted;
1067 : 3208 : trans->updated_pre_truncdrop = trans->tuples_updated;
1068 : 3208 : trans->deleted_pre_truncdrop = trans->tuples_deleted;
1069 : 3208 : trans->truncdropped = true;
1070 : : }
1071 : 3240 : }
1072 : :
1073 : : /*
1074 : : * restore counters when a truncate aborts
1075 : : */
1076 : : static void
1604 1077 : 17670 : restore_truncdrop_counters(PgStat_TableXactStatus *trans)
1078 : : {
1620 1079 [ + + ]: 17670 : if (trans->truncdropped)
1080 : : {
1081 : 219 : trans->tuples_inserted = trans->inserted_pre_truncdrop;
1082 : 219 : trans->tuples_updated = trans->updated_pre_truncdrop;
1083 : 219 : trans->tuples_deleted = trans->deleted_pre_truncdrop;
1084 : : }
1085 : 17670 : }
|