/src/postgres/src/backend/utils/activity/pgstat_xact.c
Line | Count | Source |
1 | | /* ------------------------------------------------------------------------- |
2 | | * |
3 | | * pgstat_xact.c |
4 | | * Transactional integration for the cumulative statistics system. |
5 | | * |
6 | | * Copyright (c) 2001-2026, PostgreSQL Global Development Group |
7 | | * |
8 | | * IDENTIFICATION |
9 | | * src/backend/utils/activity/pgstat_xact.c |
10 | | * ------------------------------------------------------------------------- |
11 | | */ |
12 | | |
13 | | #include "postgres.h" |
14 | | |
15 | | #include "access/xact.h" |
16 | | #include "pgstat.h" |
17 | | #include "utils/memutils.h" |
18 | | #include "utils/pgstat_internal.h" |
19 | | |
20 | | |
21 | | typedef struct PgStat_PendingDroppedStatsItem |
22 | | { |
23 | | xl_xact_stats_item item; |
24 | | bool is_create; |
25 | | dlist_node node; |
26 | | } PgStat_PendingDroppedStatsItem; |
27 | | |
28 | | |
29 | | static void AtEOXact_PgStat_DroppedStats(PgStat_SubXactStatus *xact_state, bool isCommit); |
30 | | static void AtEOSubXact_PgStat_DroppedStats(PgStat_SubXactStatus *xact_state, |
31 | | bool isCommit, int nestDepth); |
32 | | |
33 | | static PgStat_SubXactStatus *pgStatXactStack = NULL; |
34 | | |
35 | | |
36 | | /* |
37 | | * Called from access/transam/xact.c at top-level transaction commit/abort. |
38 | | */ |
39 | | void |
40 | | AtEOXact_PgStat(bool isCommit, bool parallel) |
41 | 0 | { |
42 | 0 | PgStat_SubXactStatus *xact_state; |
43 | |
|
44 | 0 | AtEOXact_PgStat_Database(isCommit, parallel); |
45 | | |
46 | | /* handle transactional stats information */ |
47 | 0 | xact_state = pgStatXactStack; |
48 | 0 | if (xact_state != NULL) |
49 | 0 | { |
50 | 0 | Assert(xact_state->nest_level == 1); |
51 | 0 | Assert(xact_state->prev == NULL); |
52 | |
|
53 | 0 | AtEOXact_PgStat_Relations(xact_state, isCommit); |
54 | 0 | AtEOXact_PgStat_DroppedStats(xact_state, isCommit); |
55 | 0 | } |
56 | 0 | pgStatXactStack = NULL; |
57 | | |
58 | | /* Make sure any stats snapshot is thrown away */ |
59 | 0 | pgstat_clear_snapshot(); |
60 | 0 | } |
61 | | |
62 | | /* |
63 | | * When committing, drop stats for objects dropped in the transaction. When |
64 | | * aborting, drop stats for objects created in the transaction. |
65 | | */ |
66 | | static void |
67 | | AtEOXact_PgStat_DroppedStats(PgStat_SubXactStatus *xact_state, bool isCommit) |
68 | 0 | { |
69 | 0 | dlist_mutable_iter iter; |
70 | 0 | int not_freed_count = 0; |
71 | |
|
72 | 0 | if (dclist_count(&xact_state->pending_drops) == 0) |
73 | 0 | return; |
74 | | |
75 | 0 | dclist_foreach_modify(iter, &xact_state->pending_drops) |
76 | 0 | { |
77 | 0 | PgStat_PendingDroppedStatsItem *pending = |
78 | 0 | dclist_container(PgStat_PendingDroppedStatsItem, node, iter.cur); |
79 | 0 | xl_xact_stats_item *it = &pending->item; |
80 | 0 | uint64 objid = ((uint64) it->objid_hi) << 32 | it->objid_lo; |
81 | |
|
82 | 0 | if (isCommit && !pending->is_create) |
83 | 0 | { |
84 | | /* |
85 | | * Transaction that dropped an object committed. Drop the stats |
86 | | * too. |
87 | | */ |
88 | 0 | if (!pgstat_drop_entry(it->kind, it->dboid, objid, true)) |
89 | 0 | not_freed_count++; |
90 | 0 | } |
91 | 0 | else if (!isCommit && pending->is_create) |
92 | 0 | { |
93 | | /* |
94 | | * Transaction that created an object aborted. Drop the stats |
95 | | * associated with the object. |
96 | | */ |
97 | 0 | if (!pgstat_drop_entry(it->kind, it->dboid, objid, true)) |
98 | 0 | not_freed_count++; |
99 | 0 | } |
100 | |
|
101 | 0 | dclist_delete_from(&xact_state->pending_drops, &pending->node); |
102 | 0 | pfree(pending); |
103 | 0 | } |
104 | |
|
105 | 0 | if (not_freed_count > 0) |
106 | 0 | pgstat_request_entry_refs_gc(); |
107 | 0 | } |
108 | | |
109 | | /* |
110 | | * Called from access/transam/xact.c at subtransaction commit/abort. |
111 | | */ |
112 | | void |
113 | | AtEOSubXact_PgStat(bool isCommit, int nestDepth) |
114 | 0 | { |
115 | 0 | PgStat_SubXactStatus *xact_state; |
116 | | |
117 | | /* merge the sub-transaction's transactional stats into the parent */ |
118 | 0 | xact_state = pgStatXactStack; |
119 | 0 | if (xact_state != NULL && |
120 | 0 | xact_state->nest_level >= nestDepth) |
121 | 0 | { |
122 | | /* delink xact_state from stack immediately to simplify reuse case */ |
123 | 0 | pgStatXactStack = xact_state->prev; |
124 | |
|
125 | 0 | AtEOSubXact_PgStat_Relations(xact_state, isCommit, nestDepth); |
126 | 0 | AtEOSubXact_PgStat_DroppedStats(xact_state, isCommit, nestDepth); |
127 | |
|
128 | 0 | pfree(xact_state); |
129 | 0 | } |
130 | 0 | } |
131 | | |
132 | | /* |
133 | | * Like AtEOXact_PgStat_DroppedStats(), but for subtransactions. |
134 | | */ |
135 | | static void |
136 | | AtEOSubXact_PgStat_DroppedStats(PgStat_SubXactStatus *xact_state, |
137 | | bool isCommit, int nestDepth) |
138 | 0 | { |
139 | 0 | PgStat_SubXactStatus *parent_xact_state; |
140 | 0 | dlist_mutable_iter iter; |
141 | 0 | int not_freed_count = 0; |
142 | |
|
143 | 0 | if (dclist_count(&xact_state->pending_drops) == 0) |
144 | 0 | return; |
145 | | |
146 | 0 | parent_xact_state = pgstat_get_xact_stack_level(nestDepth - 1); |
147 | |
|
148 | 0 | dclist_foreach_modify(iter, &xact_state->pending_drops) |
149 | 0 | { |
150 | 0 | PgStat_PendingDroppedStatsItem *pending = |
151 | 0 | dclist_container(PgStat_PendingDroppedStatsItem, node, iter.cur); |
152 | 0 | xl_xact_stats_item *it = &pending->item; |
153 | 0 | uint64 objid = ((uint64) it->objid_hi) << 32 | it->objid_lo; |
154 | |
|
155 | 0 | dclist_delete_from(&xact_state->pending_drops, &pending->node); |
156 | |
|
157 | 0 | if (!isCommit && pending->is_create) |
158 | 0 | { |
159 | | /* |
160 | | * Subtransaction creating a new stats object aborted. Drop the |
161 | | * stats object. |
162 | | */ |
163 | 0 | if (!pgstat_drop_entry(it->kind, it->dboid, objid, true)) |
164 | 0 | not_freed_count++; |
165 | 0 | pfree(pending); |
166 | 0 | } |
167 | 0 | else if (isCommit) |
168 | 0 | { |
169 | | /* |
170 | | * Subtransaction dropping a stats object committed. Can't yet |
171 | | * remove the stats object, the surrounding transaction might |
172 | | * still abort. Pass it on to the parent. |
173 | | */ |
174 | 0 | dclist_push_tail(&parent_xact_state->pending_drops, &pending->node); |
175 | 0 | } |
176 | 0 | else |
177 | 0 | { |
178 | 0 | pfree(pending); |
179 | 0 | } |
180 | 0 | } |
181 | |
|
182 | 0 | Assert(dclist_count(&xact_state->pending_drops) == 0); |
183 | 0 | if (not_freed_count > 0) |
184 | 0 | pgstat_request_entry_refs_gc(); |
185 | 0 | } |
186 | | |
187 | | /* |
188 | | * Save the transactional stats state at 2PC transaction prepare. |
189 | | */ |
190 | | void |
191 | | AtPrepare_PgStat(void) |
192 | 0 | { |
193 | 0 | PgStat_SubXactStatus *xact_state; |
194 | |
|
195 | 0 | xact_state = pgStatXactStack; |
196 | 0 | if (xact_state != NULL) |
197 | 0 | { |
198 | 0 | Assert(xact_state->nest_level == 1); |
199 | 0 | Assert(xact_state->prev == NULL); |
200 | |
|
201 | 0 | AtPrepare_PgStat_Relations(xact_state); |
202 | 0 | } |
203 | 0 | } |
204 | | |
205 | | /* |
206 | | * Clean up after successful PREPARE. |
207 | | * |
208 | | * Note: AtEOXact_PgStat is not called during PREPARE. |
209 | | */ |
210 | | void |
211 | | PostPrepare_PgStat(void) |
212 | 0 | { |
213 | 0 | PgStat_SubXactStatus *xact_state; |
214 | | |
215 | | /* |
216 | | * We don't bother to free any of the transactional state, since it's all |
217 | | * in TopTransactionContext and will go away anyway. |
218 | | */ |
219 | 0 | xact_state = pgStatXactStack; |
220 | 0 | if (xact_state != NULL) |
221 | 0 | { |
222 | 0 | Assert(xact_state->nest_level == 1); |
223 | 0 | Assert(xact_state->prev == NULL); |
224 | |
|
225 | 0 | PostPrepare_PgStat_Relations(xact_state); |
226 | 0 | } |
227 | 0 | pgStatXactStack = NULL; |
228 | | |
229 | | /* Make sure any stats snapshot is thrown away */ |
230 | 0 | pgstat_clear_snapshot(); |
231 | 0 | } |
232 | | |
233 | | /* |
234 | | * Ensure (sub)transaction stack entry for the given nest_level exists, adding |
235 | | * it if needed. |
236 | | */ |
237 | | PgStat_SubXactStatus * |
238 | | pgstat_get_xact_stack_level(int nest_level) |
239 | 0 | { |
240 | 0 | PgStat_SubXactStatus *xact_state; |
241 | |
|
242 | 0 | xact_state = pgStatXactStack; |
243 | 0 | if (xact_state == NULL || xact_state->nest_level != nest_level) |
244 | 0 | { |
245 | 0 | xact_state = (PgStat_SubXactStatus *) |
246 | 0 | MemoryContextAlloc(TopTransactionContext, |
247 | 0 | sizeof(PgStat_SubXactStatus)); |
248 | 0 | dclist_init(&xact_state->pending_drops); |
249 | 0 | xact_state->nest_level = nest_level; |
250 | 0 | xact_state->prev = pgStatXactStack; |
251 | 0 | xact_state->first = NULL; |
252 | 0 | pgStatXactStack = xact_state; |
253 | 0 | } |
254 | 0 | return xact_state; |
255 | 0 | } |
256 | | |
257 | | /* |
258 | | * Get stat items that need to be dropped at commit / abort. |
259 | | * |
260 | | * When committing, stats for objects that have been dropped in the |
261 | | * transaction are returned. When aborting, stats for newly created objects are |
262 | | * returned. |
263 | | * |
264 | | * Used by COMMIT / ABORT and 2PC PREPARE processing when building their |
265 | | * respective WAL records, to ensure stats are dropped in case of a crash / on |
266 | | * standbys. |
267 | | * |
268 | | * The list of items is allocated in CurrentMemoryContext and must be freed by |
269 | | * the caller (directly or via memory context reset). |
270 | | */ |
271 | | int |
272 | | pgstat_get_transactional_drops(bool isCommit, xl_xact_stats_item **items) |
273 | 0 | { |
274 | 0 | PgStat_SubXactStatus *xact_state = pgStatXactStack; |
275 | 0 | int nitems = 0; |
276 | 0 | dlist_iter iter; |
277 | |
|
278 | 0 | if (xact_state == NULL) |
279 | 0 | return 0; |
280 | | |
281 | | /* |
282 | | * We expect to be called for subtransaction abort (which logs a WAL |
283 | | * record), but not for subtransaction commit (which doesn't). |
284 | | */ |
285 | 0 | Assert(!isCommit || xact_state->nest_level == 1); |
286 | 0 | Assert(!isCommit || xact_state->prev == NULL); |
287 | |
|
288 | 0 | *items = palloc(dclist_count(&xact_state->pending_drops) |
289 | 0 | * sizeof(xl_xact_stats_item)); |
290 | |
|
291 | 0 | dclist_foreach(iter, &xact_state->pending_drops) |
292 | 0 | { |
293 | 0 | PgStat_PendingDroppedStatsItem *pending = |
294 | 0 | dclist_container(PgStat_PendingDroppedStatsItem, node, iter.cur); |
295 | |
|
296 | 0 | if (isCommit && pending->is_create) |
297 | 0 | continue; |
298 | 0 | if (!isCommit && !pending->is_create) |
299 | 0 | continue; |
300 | | |
301 | 0 | Assert(nitems < dclist_count(&xact_state->pending_drops)); |
302 | 0 | (*items)[nitems++] = pending->item; |
303 | 0 | } |
304 | |
|
305 | 0 | return nitems; |
306 | 0 | } |
307 | | |
308 | | /* |
309 | | * Execute scheduled drops post-commit. Called from xact_redo_commit() / |
310 | | * xact_redo_abort() during recovery, and from FinishPreparedTransaction() |
311 | | * during normal 2PC COMMIT/ABORT PREPARED processing. |
312 | | */ |
313 | | void |
314 | | pgstat_execute_transactional_drops(int ndrops, struct xl_xact_stats_item *items, bool is_redo) |
315 | 0 | { |
316 | 0 | int not_freed_count = 0; |
317 | |
|
318 | 0 | if (ndrops == 0) |
319 | 0 | return; |
320 | | |
321 | 0 | for (int i = 0; i < ndrops; i++) |
322 | 0 | { |
323 | 0 | xl_xact_stats_item *it = &items[i]; |
324 | 0 | uint64 objid = ((uint64) it->objid_hi) << 32 | it->objid_lo; |
325 | |
|
326 | 0 | if (!pgstat_drop_entry(it->kind, it->dboid, objid, true)) |
327 | 0 | not_freed_count++; |
328 | 0 | } |
329 | |
|
330 | 0 | if (not_freed_count > 0) |
331 | 0 | pgstat_request_entry_refs_gc(); |
332 | 0 | } |
333 | | |
334 | | static void |
335 | | create_drop_transactional_internal(PgStat_Kind kind, Oid dboid, uint64 objid, bool is_create) |
336 | 0 | { |
337 | 0 | int nest_level = GetCurrentTransactionNestLevel(); |
338 | 0 | PgStat_SubXactStatus *xact_state; |
339 | 0 | PgStat_PendingDroppedStatsItem *drop = (PgStat_PendingDroppedStatsItem *) |
340 | 0 | MemoryContextAlloc(TopTransactionContext, sizeof(PgStat_PendingDroppedStatsItem)); |
341 | |
|
342 | 0 | xact_state = pgstat_get_xact_stack_level(nest_level); |
343 | |
|
344 | 0 | drop->is_create = is_create; |
345 | 0 | drop->item.kind = kind; |
346 | 0 | drop->item.dboid = dboid; |
347 | 0 | drop->item.objid_lo = (uint32) objid; |
348 | 0 | drop->item.objid_hi = (uint32) (objid >> 32); |
349 | |
|
350 | 0 | dclist_push_tail(&xact_state->pending_drops, &drop->node); |
351 | 0 | } |
352 | | |
353 | | /* |
354 | | * Create a stats entry for a newly created database object in a transactional |
355 | | * manner. |
356 | | * |
357 | | * I.e. if the current (sub-)transaction aborts, the stats entry will also be |
358 | | * dropped. |
359 | | */ |
360 | | void |
361 | | pgstat_create_transactional(PgStat_Kind kind, Oid dboid, uint64 objid) |
362 | | { |
363 | | if (pgstat_get_entry_ref(kind, dboid, objid, false, NULL)) |
364 | | { |
365 | | ereport(WARNING, |
366 | | errmsg("resetting existing statistics for kind %s, db=%u, oid=%" PRIu64, |
367 | | (pgstat_get_kind_info(kind))->name, dboid, |
368 | | objid)); |
369 | | |
370 | | pgstat_reset(kind, dboid, objid); |
371 | | } |
372 | | |
373 | | create_drop_transactional_internal(kind, dboid, objid, /* create */ true); |
374 | | } |
375 | | |
376 | | /* |
377 | | * Drop a stats entry for a just dropped database object in a transactional |
378 | | * manner. |
379 | | * |
380 | | * I.e. if the current (sub-)transaction aborts, the stats entry will stay |
381 | | * alive. |
382 | | */ |
383 | | void |
384 | | pgstat_drop_transactional(PgStat_Kind kind, Oid dboid, uint64 objid) |
385 | 0 | { |
386 | | create_drop_transactional_internal(kind, dboid, objid, /* create */ false); |
387 | 0 | } |