/src/postgres/src/backend/utils/activity/pgstat_shmem.c
Line | Count | Source |
1 | | /* ------------------------------------------------------------------------- |
2 | | * |
3 | | * pgstat_shmem.c |
4 | | * Storage of stats entries in shared memory |
5 | | * |
6 | | * Copyright (c) 2001-2026, PostgreSQL Global Development Group |
7 | | * |
8 | | * IDENTIFICATION |
9 | | * src/backend/utils/activity/pgstat_shmem.c |
10 | | * ------------------------------------------------------------------------- |
11 | | */ |
12 | | |
13 | | #include "postgres.h" |
14 | | |
15 | | #include "pgstat.h" |
16 | | #include "storage/shmem.h" |
17 | | #include "storage/subsystems.h" |
18 | | #include "utils/memutils.h" |
19 | | #include "utils/pgstat_internal.h" |
20 | | |
21 | | |
22 | 0 | #define PGSTAT_ENTRY_REF_HASH_SIZE 128 |
23 | | |
24 | | /* hash table entry for finding the PgStat_EntryRef for a key */ |
25 | | typedef struct PgStat_EntryRefHashEntry |
26 | | { |
27 | | PgStat_HashKey key; /* hash key */ |
28 | | char status; /* for simplehash use */ |
29 | | PgStat_EntryRef *entry_ref; |
30 | | } PgStat_EntryRefHashEntry; |
31 | | |
32 | | |
33 | | /* for references to shared statistics entries */ |
34 | | #define SH_PREFIX pgstat_entry_ref_hash |
35 | 0 | #define SH_ELEMENT_TYPE PgStat_EntryRefHashEntry |
36 | | #define SH_KEY_TYPE PgStat_HashKey |
37 | 0 | #define SH_KEY key |
38 | | #define SH_HASH_KEY(tb, key) \ |
39 | 0 | pgstat_hash_hash_key(&key, sizeof(PgStat_HashKey), NULL) |
40 | | #define SH_EQUAL(tb, a, b) \ |
41 | 0 | pgstat_cmp_hash_key(&a, &b, sizeof(PgStat_HashKey), NULL) == 0 |
42 | | #define SH_SCOPE static inline |
43 | | #define SH_DEFINE |
44 | | #define SH_DECLARE |
45 | | #include "lib/simplehash.h" |
46 | | |
47 | | |
48 | | static void pgstat_drop_database_and_contents(Oid dboid); |
49 | | |
50 | | static void pgstat_free_entry(PgStatShared_HashEntry *shent, dshash_seq_status *hstat); |
51 | | |
52 | | static void pgstat_release_entry_ref(PgStat_HashKey key, PgStat_EntryRef *entry_ref, bool discard_pending); |
53 | | static bool pgstat_need_entry_refs_gc(void); |
54 | | static void pgstat_gc_entry_refs(void); |
55 | | static void pgstat_release_all_entry_refs(bool discard_pending); |
56 | | typedef bool (*ReleaseMatchCB) (PgStat_EntryRefHashEntry *, Datum data); |
57 | | static void pgstat_release_matching_entry_refs(bool discard_pending, ReleaseMatchCB match, Datum match_data); |
58 | | |
59 | | static void pgstat_setup_memcxt(void); |
60 | | |
61 | | static void StatsShmemRequest(void *arg); |
62 | | static void StatsShmemInit(void *arg); |
63 | | |
64 | | const ShmemCallbacks StatsShmemCallbacks = { |
65 | | .request_fn = StatsShmemRequest, |
66 | | .init_fn = StatsShmemInit, |
67 | | }; |
68 | | |
69 | | /* parameter for the shared hash */ |
70 | | static const dshash_parameters dsh_params = { |
71 | | sizeof(PgStat_HashKey), |
72 | | sizeof(PgStatShared_HashEntry), |
73 | | pgstat_cmp_hash_key, |
74 | | pgstat_hash_hash_key, |
75 | | dshash_memcpy, |
76 | | LWTRANCHE_PGSTATS_HASH |
77 | | }; |
78 | | |
79 | | |
80 | | /* |
81 | | * Backend local references to shared stats entries. If there are pending |
82 | | * updates to a stats entry, the PgStat_EntryRef is added to the pgStatPending |
83 | | * list. |
84 | | * |
85 | | * When a stats entry is dropped each backend needs to release its reference |
86 | | * to it before the memory can be released. To trigger that |
87 | | * pgStatLocal.shmem->gc_request_count is incremented - which each backend |
88 | | * compares to their copy of pgStatSharedRefAge on a regular basis. |
89 | | */ |
90 | | static pgstat_entry_ref_hash_hash *pgStatEntryRefHash = NULL; |
91 | | static int pgStatSharedRefAge = 0; /* cache age of pgStatLocal.shmem */ |
92 | | |
93 | | /* |
94 | | * Memory contexts containing the pgStatEntryRefHash table and the |
95 | | * pgStatSharedRef entries respectively. Kept separate to make it easier to |
96 | | * track / attribute memory usage. |
97 | | */ |
98 | | static MemoryContext pgStatSharedRefContext = NULL; |
99 | | static MemoryContext pgStatEntryRefHashContext = NULL; |
100 | | |
101 | | |
102 | | /* ------------------------------------------------------------ |
103 | | * Public functions called from postmaster follow |
104 | | * ------------------------------------------------------------ |
105 | | */ |
106 | | |
107 | | /* |
108 | | * The size of the shared memory allocation for stats stored in the shared |
109 | | * stats hash table. This allocation will be done as part of the main shared |
110 | | * memory, rather than dynamic shared memory, allowing it to be initialized in |
111 | | * postmaster. |
112 | | */ |
113 | | static Size |
114 | | pgstat_dsa_init_size(void) |
115 | 0 | { |
116 | 0 | Size sz; |
117 | | |
118 | | /* |
119 | | * The dshash header / initial buckets array needs to fit into "plain" |
120 | | * shared memory, but it's beneficial to not need dsm segments |
121 | | * immediately. A size of 256kB seems works well and is not |
122 | | * disproportional compared to other constant sized shared memory |
123 | | * allocations. NB: To avoid DSMs further, the user can configure |
124 | | * min_dynamic_shared_memory. |
125 | | */ |
126 | 0 | sz = 256 * 1024; |
127 | 0 | Assert(dsa_minimum_size() <= sz); |
128 | 0 | return MAXALIGN(sz); |
129 | 0 | } |
130 | | |
131 | | /* |
132 | | * Compute shared memory space needed for cumulative statistics |
133 | | */ |
134 | | static Size |
135 | | StatsShmemSize(void) |
136 | 0 | { |
137 | 0 | Size sz; |
138 | |
|
139 | 0 | sz = MAXALIGN(sizeof(PgStat_ShmemControl)); |
140 | 0 | sz = add_size(sz, pgstat_dsa_init_size()); |
141 | | |
142 | | /* Add shared memory for all the custom fixed-numbered statistics */ |
143 | 0 | for (PgStat_Kind kind = PGSTAT_KIND_CUSTOM_MIN; kind <= PGSTAT_KIND_CUSTOM_MAX; kind++) |
144 | 0 | { |
145 | 0 | const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind); |
146 | |
|
147 | 0 | if (!kind_info) |
148 | 0 | continue; |
149 | 0 | if (!kind_info->fixed_amount) |
150 | 0 | continue; |
151 | | |
152 | 0 | Assert(kind_info->shared_size != 0); |
153 | 0 | sz = add_size(sz, MAXALIGN(kind_info->shared_size)); |
154 | 0 | } |
155 | |
|
156 | 0 | return sz; |
157 | 0 | } |
158 | | |
159 | | /* |
160 | | * Register shared memory area for cumulative statistics |
161 | | */ |
162 | | static void |
163 | | StatsShmemRequest(void *arg) |
164 | 0 | { |
165 | 0 | ShmemRequestStruct(.name = "Shared Memory Stats", |
166 | 0 | .size = StatsShmemSize(), |
167 | 0 | .ptr = (void **) &pgStatLocal.shmem, |
168 | 0 | ); |
169 | 0 | } |
170 | | |
171 | | /* |
172 | | * Initialize cumulative statistics system during startup |
173 | | */ |
174 | | static void |
175 | | StatsShmemInit(void *arg) |
176 | 0 | { |
177 | 0 | dsa_area *dsa; |
178 | 0 | dshash_table *dsh; |
179 | 0 | PgStat_ShmemControl *ctl = pgStatLocal.shmem; |
180 | 0 | char *p = (char *) ctl; |
181 | | |
182 | | /* the allocation of pgStatLocal.shmem itself */ |
183 | 0 | p += MAXALIGN(sizeof(PgStat_ShmemControl)); |
184 | | |
185 | | /* |
186 | | * Create a small dsa allocation in plain shared memory. This is required |
187 | | * because postmaster cannot use dsm segments. It also provides a small |
188 | | * efficiency win. |
189 | | */ |
190 | 0 | ctl->raw_dsa_area = p; |
191 | 0 | p += pgstat_dsa_init_size(); |
192 | 0 | dsa = dsa_create_in_place(ctl->raw_dsa_area, |
193 | 0 | pgstat_dsa_init_size(), |
194 | 0 | LWTRANCHE_PGSTATS_DSA, NULL); |
195 | 0 | dsa_pin(dsa); |
196 | | |
197 | | /* |
198 | | * To ensure dshash is created in "plain" shared memory, temporarily limit |
199 | | * size of dsa to the initial size of the dsa. |
200 | | */ |
201 | 0 | dsa_set_size_limit(dsa, pgstat_dsa_init_size()); |
202 | | |
203 | | /* |
204 | | * With the limit in place, create the dshash table. XXX: It'd be nice if |
205 | | * there were dshash_create_in_place(). |
206 | | */ |
207 | 0 | dsh = dshash_create(dsa, &dsh_params, NULL); |
208 | 0 | ctl->hash_handle = dshash_get_hash_table_handle(dsh); |
209 | | |
210 | | /* lift limit set above */ |
211 | 0 | dsa_set_size_limit(dsa, -1); |
212 | | |
213 | | /* |
214 | | * Postmaster will never access these again, thus free the local |
215 | | * dsa/dshash references. |
216 | | */ |
217 | 0 | dshash_detach(dsh); |
218 | 0 | dsa_detach(dsa); |
219 | |
|
220 | 0 | pg_atomic_init_u64(&ctl->gc_request_count, 1); |
221 | | |
222 | | /* Do the per-kind initialization */ |
223 | 0 | for (PgStat_Kind kind = PGSTAT_KIND_MIN; kind <= PGSTAT_KIND_MAX; kind++) |
224 | 0 | { |
225 | 0 | const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind); |
226 | 0 | char *ptr; |
227 | |
|
228 | 0 | if (!kind_info) |
229 | 0 | continue; |
230 | | |
231 | | /* initialize entry count tracking */ |
232 | 0 | if (kind_info->track_entry_count) |
233 | 0 | pg_atomic_init_u64(&ctl->entry_counts[kind - 1], 0); |
234 | | |
235 | | /* initialize fixed-numbered stats */ |
236 | 0 | if (kind_info->fixed_amount) |
237 | 0 | { |
238 | 0 | if (pgstat_is_kind_builtin(kind)) |
239 | 0 | ptr = ((char *) ctl) + kind_info->shared_ctl_off; |
240 | 0 | else |
241 | 0 | { |
242 | 0 | int idx = kind - PGSTAT_KIND_CUSTOM_MIN; |
243 | |
|
244 | 0 | Assert(kind_info->shared_size != 0); |
245 | 0 | ctl->custom_data[idx] = p; |
246 | 0 | p += MAXALIGN(kind_info->shared_size); |
247 | 0 | ptr = ctl->custom_data[idx]; |
248 | 0 | } |
249 | |
|
250 | 0 | kind_info->init_shmem_cb(ptr); |
251 | 0 | } |
252 | 0 | } |
253 | 0 | } |
254 | | |
255 | | void |
256 | | pgstat_attach_shmem(void) |
257 | 0 | { |
258 | 0 | MemoryContext oldcontext; |
259 | |
|
260 | 0 | Assert(pgStatLocal.dsa == NULL); |
261 | | |
262 | | /* stats shared memory persists for the backend lifetime */ |
263 | 0 | oldcontext = MemoryContextSwitchTo(TopMemoryContext); |
264 | |
|
265 | 0 | pgStatLocal.dsa = dsa_attach_in_place(pgStatLocal.shmem->raw_dsa_area, |
266 | 0 | NULL); |
267 | 0 | dsa_pin_mapping(pgStatLocal.dsa); |
268 | |
|
269 | 0 | pgStatLocal.shared_hash = dshash_attach(pgStatLocal.dsa, &dsh_params, |
270 | 0 | pgStatLocal.shmem->hash_handle, |
271 | 0 | NULL); |
272 | |
|
273 | 0 | MemoryContextSwitchTo(oldcontext); |
274 | 0 | } |
275 | | |
276 | | void |
277 | | pgstat_detach_shmem(void) |
278 | 0 | { |
279 | 0 | Assert(pgStatLocal.dsa); |
280 | | |
281 | | /* we shouldn't leave references to shared stats */ |
282 | 0 | pgstat_release_all_entry_refs(false); |
283 | |
|
284 | 0 | dshash_detach(pgStatLocal.shared_hash); |
285 | 0 | pgStatLocal.shared_hash = NULL; |
286 | |
|
287 | 0 | dsa_detach(pgStatLocal.dsa); |
288 | | |
289 | | /* |
290 | | * dsa_detach() does not decrement the DSA reference count as no segment |
291 | | * was provided to dsa_attach_in_place(), causing no cleanup callbacks to |
292 | | * be registered. Hence, release it manually now. |
293 | | */ |
294 | 0 | dsa_release_in_place(pgStatLocal.shmem->raw_dsa_area); |
295 | |
|
296 | 0 | pgStatLocal.dsa = NULL; |
297 | 0 | } |
298 | | |
299 | | |
300 | | /* ------------------------------------------------------------ |
301 | | * Maintenance of shared memory stats entries |
302 | | * ------------------------------------------------------------ |
303 | | */ |
304 | | |
305 | | /* |
306 | | * Initialize entry newly-created. |
307 | | * |
308 | | * Returns NULL in the event of an allocation failure, so as callers can |
309 | | * take cleanup actions as the entry initialized is already inserted in the |
310 | | * shared hashtable. |
311 | | */ |
312 | | PgStatShared_Common * |
313 | | pgstat_init_entry(PgStat_Kind kind, |
314 | | PgStatShared_HashEntry *shhashent) |
315 | 0 | { |
316 | | /* Create new stats entry. */ |
317 | 0 | dsa_pointer chunk; |
318 | 0 | PgStatShared_Common *shheader; |
319 | 0 | const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind); |
320 | | |
321 | | /* |
322 | | * Initialize refcount to 1, marking it as valid / not dropped. The entry |
323 | | * can't be freed before the initialization because it can't be found as |
324 | | * long as we hold the dshash partition lock. Caller needs to increase |
325 | | * further if a longer lived reference is needed. |
326 | | */ |
327 | 0 | pg_atomic_init_u32(&shhashent->refcount, 1); |
328 | | |
329 | | /* |
330 | | * Initialize "generation" to 0, as freshly created. |
331 | | */ |
332 | 0 | pg_atomic_init_u32(&shhashent->generation, 0); |
333 | 0 | shhashent->dropped = false; |
334 | |
|
335 | 0 | chunk = dsa_allocate_extended(pgStatLocal.dsa, |
336 | 0 | kind_info->shared_size, |
337 | 0 | DSA_ALLOC_ZERO | DSA_ALLOC_NO_OOM); |
338 | 0 | if (chunk == InvalidDsaPointer) |
339 | 0 | return NULL; |
340 | | |
341 | 0 | shheader = dsa_get_address(pgStatLocal.dsa, chunk); |
342 | 0 | shheader->magic = 0xdeadbeef; |
343 | | |
344 | | /* Link the new entry from the hash entry. */ |
345 | 0 | shhashent->body = chunk; |
346 | | |
347 | | /* Increment entry count, if required. */ |
348 | 0 | if (kind_info->track_entry_count) |
349 | 0 | pg_atomic_fetch_add_u64(&pgStatLocal.shmem->entry_counts[kind - 1], 1); |
350 | |
|
351 | 0 | LWLockInitialize(&shheader->lock, LWTRANCHE_PGSTATS_DATA); |
352 | |
|
353 | 0 | return shheader; |
354 | 0 | } |
355 | | |
356 | | static PgStatShared_Common * |
357 | | pgstat_reinit_entry(PgStat_Kind kind, PgStatShared_HashEntry *shhashent) |
358 | 0 | { |
359 | 0 | PgStatShared_Common *shheader; |
360 | |
|
361 | 0 | shheader = dsa_get_address(pgStatLocal.dsa, shhashent->body); |
362 | | |
363 | | /* mark as not dropped anymore */ |
364 | 0 | pg_atomic_fetch_add_u32(&shhashent->refcount, 1); |
365 | | |
366 | | /* |
367 | | * Increment "generation", to let any backend with local references know |
368 | | * that what they point to is outdated. |
369 | | */ |
370 | 0 | pg_atomic_fetch_add_u32(&shhashent->generation, 1); |
371 | 0 | shhashent->dropped = false; |
372 | | |
373 | | /* reinitialize content */ |
374 | 0 | Assert(shheader->magic == 0xdeadbeef); |
375 | 0 | memset(pgstat_get_entry_data(kind, shheader), 0, |
376 | 0 | pgstat_get_entry_len(kind)); |
377 | |
|
378 | 0 | return shheader; |
379 | 0 | } |
380 | | |
381 | | static void |
382 | | pgstat_setup_shared_refs(void) |
383 | 0 | { |
384 | 0 | if (likely(pgStatEntryRefHash != NULL)) |
385 | 0 | return; |
386 | | |
387 | 0 | pgStatEntryRefHash = |
388 | 0 | pgstat_entry_ref_hash_create(pgStatEntryRefHashContext, |
389 | 0 | PGSTAT_ENTRY_REF_HASH_SIZE, NULL); |
390 | 0 | pgStatSharedRefAge = pg_atomic_read_u64(&pgStatLocal.shmem->gc_request_count); |
391 | 0 | Assert(pgStatSharedRefAge != 0); |
392 | 0 | } |
393 | | |
394 | | /* |
395 | | * Helper function for pgstat_get_entry_ref(). |
396 | | */ |
397 | | static void |
398 | | pgstat_acquire_entry_ref(PgStat_EntryRef *entry_ref, |
399 | | PgStatShared_HashEntry *shhashent, |
400 | | PgStatShared_Common *shheader) |
401 | 0 | { |
402 | 0 | Assert(shheader->magic == 0xdeadbeef); |
403 | 0 | Assert(pg_atomic_read_u32(&shhashent->refcount) > 0); |
404 | |
|
405 | 0 | pg_atomic_fetch_add_u32(&shhashent->refcount, 1); |
406 | |
|
407 | 0 | entry_ref->shared_stats = shheader; |
408 | 0 | entry_ref->shared_entry = shhashent; |
409 | 0 | entry_ref->generation = pg_atomic_read_u32(&shhashent->generation); |
410 | | |
411 | | /* |
412 | | * Complete the local reference before releasing the lock. Releasing an |
413 | | * LWLock can process a pending interrupt, and callers may catch the |
414 | | * resulting error and continue using the backend-local cache. |
415 | | */ |
416 | 0 | dshash_release_lock(pgStatLocal.shared_hash, shhashent); |
417 | 0 | } |
418 | | |
419 | | /* |
420 | | * Helper function for pgstat_get_entry_ref(). |
421 | | */ |
422 | | static bool |
423 | | pgstat_get_entry_ref_cached(PgStat_HashKey key, PgStat_EntryRef **entry_ref_p) |
424 | 0 | { |
425 | 0 | bool found; |
426 | 0 | PgStat_EntryRefHashEntry *cache_entry; |
427 | | |
428 | | /* |
429 | | * We immediately insert a cache entry, because it avoids 1) multiple |
430 | | * hashtable lookups in case of a cache miss 2) having to deal with |
431 | | * out-of-memory errors after incrementing PgStatShared_Common->refcount. |
432 | | */ |
433 | |
|
434 | 0 | cache_entry = pgstat_entry_ref_hash_insert(pgStatEntryRefHash, key, &found); |
435 | |
|
436 | 0 | if (!found || !cache_entry->entry_ref) |
437 | 0 | { |
438 | 0 | PgStat_EntryRef *entry_ref; |
439 | |
|
440 | 0 | entry_ref = MemoryContextAllocExtended(pgStatSharedRefContext, |
441 | 0 | sizeof(PgStat_EntryRef), |
442 | 0 | MCXT_ALLOC_NO_OOM); |
443 | 0 | if (unlikely(entry_ref == NULL)) |
444 | 0 | { |
445 | | /* |
446 | | * Clean the hash entry to keep the table consistent in the |
447 | | * backend. |
448 | | */ |
449 | 0 | pgstat_entry_ref_hash_delete(pgStatEntryRefHash, key); |
450 | |
|
451 | 0 | ereport(ERROR, |
452 | 0 | (errcode(ERRCODE_OUT_OF_MEMORY), |
453 | 0 | errmsg("out of memory"))); |
454 | 0 | } |
455 | | |
456 | 0 | cache_entry->entry_ref = entry_ref; |
457 | 0 | entry_ref->shared_stats = NULL; |
458 | 0 | entry_ref->shared_entry = NULL; |
459 | 0 | entry_ref->pending = NULL; |
460 | |
|
461 | 0 | found = false; |
462 | 0 | } |
463 | 0 | else if (cache_entry->entry_ref->shared_stats == NULL) |
464 | 0 | { |
465 | 0 | Assert(cache_entry->entry_ref->pending == NULL); |
466 | 0 | found = false; |
467 | 0 | } |
468 | 0 | else |
469 | 0 | { |
470 | 0 | PgStat_EntryRef *entry_ref PG_USED_FOR_ASSERTS_ONLY; |
471 | |
|
472 | 0 | entry_ref = cache_entry->entry_ref; |
473 | 0 | Assert(entry_ref->shared_entry != NULL); |
474 | 0 | Assert(entry_ref->shared_stats != NULL); |
475 | |
|
476 | 0 | Assert(entry_ref->shared_stats->magic == 0xdeadbeef); |
477 | | /* should have at least our reference */ |
478 | 0 | Assert(pg_atomic_read_u32(&entry_ref->shared_entry->refcount) > 0); |
479 | 0 | } |
480 | | |
481 | 0 | *entry_ref_p = cache_entry->entry_ref; |
482 | 0 | return found; |
483 | 0 | } |
484 | | |
485 | | /* |
486 | | * Get a shared stats reference. If create is true, the shared stats object is |
487 | | * created if it does not exist. |
488 | | * |
489 | | * When create is true, and created_entry is non-NULL, it'll be set to true |
490 | | * if the entry is newly created, false otherwise. |
491 | | */ |
492 | | PgStat_EntryRef * |
493 | | pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create, |
494 | | bool *created_entry) |
495 | 0 | { |
496 | 0 | PgStat_HashKey key = {0}; |
497 | 0 | PgStatShared_HashEntry *shhashent; |
498 | 0 | PgStatShared_Common *shheader = NULL; |
499 | 0 | PgStat_EntryRef *entry_ref; |
500 | |
|
501 | 0 | key.kind = kind; |
502 | 0 | key.dboid = dboid; |
503 | 0 | key.objid = objid; |
504 | | |
505 | | /* |
506 | | * passing in created_entry only makes sense if we possibly could create |
507 | | * entry. |
508 | | */ |
509 | 0 | Assert(create || created_entry == NULL); |
510 | 0 | pgstat_assert_is_up(); |
511 | 0 | Assert(pgStatLocal.shared_hash != NULL); |
512 | 0 | Assert(!pgStatLocal.shmem->is_shutdown); |
513 | |
|
514 | 0 | pgstat_setup_memcxt(); |
515 | 0 | pgstat_setup_shared_refs(); |
516 | |
|
517 | 0 | if (created_entry != NULL) |
518 | 0 | *created_entry = false; |
519 | | |
520 | | /* |
521 | | * Check if other backends dropped stats that could not be deleted because |
522 | | * somebody held references to it. If so, check this backend's references. |
523 | | * This is not expected to happen often. The location of the check is a |
524 | | * bit random, but this is a relatively frequently called path, so better |
525 | | * than most. |
526 | | */ |
527 | 0 | if (pgstat_need_entry_refs_gc()) |
528 | 0 | pgstat_gc_entry_refs(); |
529 | | |
530 | | /* |
531 | | * First check the lookup cache hashtable in local memory. If we find a |
532 | | * match here we can avoid taking locks / causing contention. |
533 | | */ |
534 | 0 | if (pgstat_get_entry_ref_cached(key, &entry_ref)) |
535 | 0 | return entry_ref; |
536 | | |
537 | 0 | Assert(entry_ref != NULL); |
538 | | |
539 | | /* |
540 | | * Do a lookup in the hash table first - it's quite likely that the entry |
541 | | * already exists, and that way we only need a shared lock. |
542 | | */ |
543 | 0 | shhashent = dshash_find(pgStatLocal.shared_hash, &key, false); |
544 | |
|
545 | 0 | if (create && !shhashent) |
546 | 0 | { |
547 | 0 | bool shfound; |
548 | | |
549 | | /* |
550 | | * It's possible that somebody created the entry since the above |
551 | | * lookup. If so, fall through to the same path as if we'd have if it |
552 | | * already had been created before the dshash_find() calls. |
553 | | */ |
554 | 0 | shhashent = dshash_find_or_insert_extended(pgStatLocal.shared_hash, |
555 | 0 | &key, &shfound, |
556 | 0 | DSHASH_INSERT_NO_OOM); |
557 | 0 | if (!shhashent) |
558 | 0 | { |
559 | | /* |
560 | | * Clean up the local reference when failing insert into the |
561 | | * shared hashtable. |
562 | | */ |
563 | 0 | pgstat_release_entry_ref(key, entry_ref, false); |
564 | 0 | ereport(ERROR, |
565 | 0 | (errcode(ERRCODE_OUT_OF_MEMORY), |
566 | 0 | errmsg("out of memory"), |
567 | 0 | errdetail("Failed while inserting entry %u/%u/%" PRIu64 ".", |
568 | 0 | key.kind, key.dboid, key.objid))); |
569 | 0 | } |
570 | | |
571 | 0 | if (!shfound) |
572 | 0 | { |
573 | 0 | shheader = pgstat_init_entry(kind, shhashent); |
574 | 0 | if (shheader == NULL) |
575 | 0 | { |
576 | | /* |
577 | | * Failed the allocation of a new entry, so clean up both the |
578 | | * local reference and the shared hashtable before giving up. |
579 | | * Clean the local state first, since releasing the dshash |
580 | | * lock can process a pending interrupt. |
581 | | */ |
582 | 0 | pgstat_release_entry_ref(key, entry_ref, false); |
583 | 0 | dshash_delete_entry(pgStatLocal.shared_hash, shhashent); |
584 | |
|
585 | 0 | ereport(ERROR, |
586 | 0 | (errcode(ERRCODE_OUT_OF_MEMORY), |
587 | 0 | errmsg("out of memory"), |
588 | 0 | errdetail("Failed while allocating entry %u/%u/%" PRIu64 ".", |
589 | 0 | key.kind, key.dboid, key.objid))); |
590 | 0 | } |
591 | 0 | pgstat_acquire_entry_ref(entry_ref, shhashent, shheader); |
592 | |
|
593 | 0 | if (created_entry != NULL) |
594 | 0 | *created_entry = true; |
595 | |
|
596 | 0 | return entry_ref; |
597 | 0 | } |
598 | 0 | } |
599 | | |
600 | 0 | if (!shhashent) |
601 | 0 | { |
602 | | /* |
603 | | * If we're not creating, delete the reference again. In all |
604 | | * likelihood it's just a stats lookup - no point wasting memory for a |
605 | | * shared ref to nothing... |
606 | | */ |
607 | 0 | pgstat_release_entry_ref(key, entry_ref, false); |
608 | |
|
609 | 0 | return NULL; |
610 | 0 | } |
611 | 0 | else |
612 | 0 | { |
613 | | /* |
614 | | * Can get here either because dshash_find() found a match, or if |
615 | | * dshash_find_or_insert() found a concurrently inserted entry. |
616 | | */ |
617 | |
|
618 | 0 | if (shhashent->dropped && create) |
619 | 0 | { |
620 | | /* |
621 | | * There are legitimate cases where the old stats entry might not |
622 | | * yet have been dropped by the time it's reused. The most obvious |
623 | | * case are replication slot stats, where a new slot can be |
624 | | * created with the same index just after dropping. But oid |
625 | | * wraparound can lead to other cases as well. We just reset the |
626 | | * stats to their plain state, while incrementing its "generation" |
627 | | * in the shared entry for any remaining local references. |
628 | | */ |
629 | 0 | shheader = pgstat_reinit_entry(kind, shhashent); |
630 | 0 | pgstat_acquire_entry_ref(entry_ref, shhashent, shheader); |
631 | |
|
632 | 0 | if (created_entry != NULL) |
633 | 0 | *created_entry = true; |
634 | |
|
635 | 0 | return entry_ref; |
636 | 0 | } |
637 | 0 | else if (shhashent->dropped) |
638 | 0 | { |
639 | 0 | dshash_release_lock(pgStatLocal.shared_hash, shhashent); |
640 | 0 | pgstat_release_entry_ref(key, entry_ref, false); |
641 | |
|
642 | 0 | return NULL; |
643 | 0 | } |
644 | 0 | else |
645 | 0 | { |
646 | 0 | shheader = dsa_get_address(pgStatLocal.dsa, shhashent->body); |
647 | 0 | pgstat_acquire_entry_ref(entry_ref, shhashent, shheader); |
648 | |
|
649 | 0 | return entry_ref; |
650 | 0 | } |
651 | 0 | } |
652 | 0 | } |
653 | | |
654 | | static void |
655 | | pgstat_release_entry_ref(PgStat_HashKey key, PgStat_EntryRef *entry_ref, |
656 | | bool discard_pending) |
657 | 0 | { |
658 | 0 | if (entry_ref && entry_ref->pending) |
659 | 0 | { |
660 | 0 | if (discard_pending) |
661 | 0 | pgstat_delete_pending_entry(entry_ref); |
662 | 0 | else |
663 | 0 | elog(ERROR, "releasing ref with pending data"); |
664 | 0 | } |
665 | | |
666 | 0 | if (entry_ref && entry_ref->shared_stats) |
667 | 0 | { |
668 | 0 | Assert(entry_ref->shared_stats->magic == 0xdeadbeef); |
669 | 0 | Assert(entry_ref->pending == NULL); |
670 | | |
671 | | /* |
672 | | * This can't race with another backend looking up the stats entry and |
673 | | * increasing the refcount because it is not "legal" to create |
674 | | * additional references to dropped entries. |
675 | | */ |
676 | 0 | if (pg_atomic_fetch_sub_u32(&entry_ref->shared_entry->refcount, 1) == 1) |
677 | 0 | { |
678 | 0 | PgStatShared_HashEntry *shent; |
679 | | |
680 | | /* |
681 | | * We're the last referrer to this entry, try to drop the shared |
682 | | * entry. |
683 | | */ |
684 | | |
685 | | /* only dropped entries can reach a 0 refcount */ |
686 | 0 | Assert(entry_ref->shared_entry->dropped); |
687 | |
|
688 | 0 | shent = dshash_find(pgStatLocal.shared_hash, |
689 | 0 | &entry_ref->shared_entry->key, |
690 | 0 | true); |
691 | 0 | if (!shent) |
692 | 0 | elog(ERROR, "could not find just referenced shared stats entry"); |
693 | | |
694 | | /* |
695 | | * This entry may have been reinitialized while trying to release |
696 | | * it, so double-check that it has not been reused while holding a |
697 | | * lock on its shared entry. |
698 | | */ |
699 | 0 | if (pg_atomic_read_u32(&entry_ref->shared_entry->generation) == |
700 | 0 | entry_ref->generation) |
701 | 0 | { |
702 | | /* Same "generation", so we're OK with the removal */ |
703 | 0 | Assert(pg_atomic_read_u32(&entry_ref->shared_entry->refcount) == 0); |
704 | 0 | Assert(entry_ref->shared_entry == shent); |
705 | 0 | pgstat_free_entry(shent, NULL); |
706 | 0 | } |
707 | 0 | else |
708 | 0 | { |
709 | | /* |
710 | | * Shared stats entry has been reinitialized, so do not drop |
711 | | * its shared entry, only release its lock. |
712 | | */ |
713 | 0 | dshash_release_lock(pgStatLocal.shared_hash, shent); |
714 | 0 | } |
715 | 0 | } |
716 | 0 | } |
717 | | |
718 | 0 | if (!pgstat_entry_ref_hash_delete(pgStatEntryRefHash, key)) |
719 | 0 | elog(ERROR, "entry ref vanished before deletion"); |
720 | | |
721 | 0 | if (entry_ref) |
722 | 0 | pfree(entry_ref); |
723 | 0 | } |
724 | | |
725 | | /* |
726 | | * Acquire exclusive lock on the entry. |
727 | | * |
728 | | * If nowait is true, it's just a conditional acquire, and the result |
729 | | * *must* be checked to verify success. |
730 | | * If nowait is false, waits as necessary, always returning true. |
731 | | */ |
732 | | bool |
733 | | pgstat_lock_entry(PgStat_EntryRef *entry_ref, bool nowait) |
734 | 0 | { |
735 | 0 | LWLock *lock = &entry_ref->shared_stats->lock; |
736 | |
|
737 | 0 | if (nowait) |
738 | 0 | return LWLockConditionalAcquire(lock, LW_EXCLUSIVE); |
739 | | |
740 | 0 | LWLockAcquire(lock, LW_EXCLUSIVE); |
741 | 0 | return true; |
742 | 0 | } |
743 | | |
744 | | /* |
745 | | * Acquire shared lock on the entry. |
746 | | * |
747 | | * Separate from pgstat_lock_entry() as most callers will need to lock |
748 | | * exclusively. The wait semantics are identical. |
749 | | */ |
750 | | bool |
751 | | pgstat_lock_entry_shared(PgStat_EntryRef *entry_ref, bool nowait) |
752 | 0 | { |
753 | 0 | LWLock *lock = &entry_ref->shared_stats->lock; |
754 | |
|
755 | 0 | if (nowait) |
756 | 0 | return LWLockConditionalAcquire(lock, LW_SHARED); |
757 | | |
758 | 0 | LWLockAcquire(lock, LW_SHARED); |
759 | 0 | return true; |
760 | 0 | } |
761 | | |
762 | | void |
763 | | pgstat_unlock_entry(PgStat_EntryRef *entry_ref) |
764 | 0 | { |
765 | 0 | LWLockRelease(&entry_ref->shared_stats->lock); |
766 | 0 | } |
767 | | |
768 | | /* |
769 | | * Helper function to fetch and lock shared stats. |
770 | | */ |
771 | | PgStat_EntryRef * |
772 | | pgstat_get_entry_ref_locked(PgStat_Kind kind, Oid dboid, uint64 objid, |
773 | | bool nowait) |
774 | 0 | { |
775 | 0 | PgStat_EntryRef *entry_ref; |
776 | | |
777 | | /* find shared table stats entry corresponding to the local entry */ |
778 | 0 | entry_ref = pgstat_get_entry_ref(kind, dboid, objid, true, NULL); |
779 | | |
780 | | /* lock the shared entry to protect the content, skip if failed */ |
781 | 0 | if (!pgstat_lock_entry(entry_ref, nowait)) |
782 | 0 | return NULL; |
783 | | |
784 | 0 | return entry_ref; |
785 | 0 | } |
786 | | |
787 | | void |
788 | | pgstat_request_entry_refs_gc(void) |
789 | 0 | { |
790 | 0 | pg_atomic_fetch_add_u64(&pgStatLocal.shmem->gc_request_count, 1); |
791 | 0 | } |
792 | | |
793 | | static bool |
794 | | pgstat_need_entry_refs_gc(void) |
795 | 0 | { |
796 | 0 | uint64 curage; |
797 | |
|
798 | 0 | if (!pgStatEntryRefHash) |
799 | 0 | return false; |
800 | | |
801 | | /* should have been initialized when creating pgStatEntryRefHash */ |
802 | 0 | Assert(pgStatSharedRefAge != 0); |
803 | |
|
804 | 0 | curage = pg_atomic_read_u64(&pgStatLocal.shmem->gc_request_count); |
805 | |
|
806 | 0 | return pgStatSharedRefAge != curage; |
807 | 0 | } |
808 | | |
809 | | static void |
810 | | pgstat_gc_entry_refs(void) |
811 | 0 | { |
812 | 0 | pgstat_entry_ref_hash_iterator i; |
813 | 0 | PgStat_EntryRefHashEntry *ent; |
814 | 0 | uint64 curage; |
815 | |
|
816 | 0 | curage = pg_atomic_read_u64(&pgStatLocal.shmem->gc_request_count); |
817 | 0 | Assert(curage != 0); |
818 | | |
819 | | /* |
820 | | * Some entries have been dropped or reinitialized. Invalidate cache |
821 | | * pointer to them. |
822 | | */ |
823 | 0 | pgstat_entry_ref_hash_start_iterate(pgStatEntryRefHash, &i); |
824 | 0 | while ((ent = pgstat_entry_ref_hash_iterate(pgStatEntryRefHash, &i)) != NULL) |
825 | 0 | { |
826 | 0 | PgStat_EntryRef *entry_ref = ent->entry_ref; |
827 | |
|
828 | 0 | Assert(!entry_ref->shared_stats || |
829 | 0 | entry_ref->shared_stats->magic == 0xdeadbeef); |
830 | | |
831 | | /* |
832 | | * "generation" checks for the case of entries being reinitialized, |
833 | | * and "dropped" for the case where these are.. dropped. |
834 | | */ |
835 | 0 | if (!entry_ref->shared_entry->dropped && |
836 | 0 | pg_atomic_read_u32(&entry_ref->shared_entry->generation) == |
837 | 0 | entry_ref->generation) |
838 | 0 | continue; |
839 | | |
840 | | /* cannot gc shared ref that has pending data */ |
841 | 0 | if (entry_ref->pending != NULL) |
842 | 0 | continue; |
843 | | |
844 | 0 | pgstat_release_entry_ref(ent->key, entry_ref, false); |
845 | 0 | } |
846 | |
|
847 | 0 | pgStatSharedRefAge = curage; |
848 | 0 | } |
849 | | |
850 | | static void |
851 | | pgstat_release_matching_entry_refs(bool discard_pending, ReleaseMatchCB match, |
852 | | Datum match_data) |
853 | 0 | { |
854 | 0 | pgstat_entry_ref_hash_iterator i; |
855 | 0 | PgStat_EntryRefHashEntry *ent; |
856 | |
|
857 | 0 | if (pgStatEntryRefHash == NULL) |
858 | 0 | return; |
859 | | |
860 | 0 | pgstat_entry_ref_hash_start_iterate(pgStatEntryRefHash, &i); |
861 | |
|
862 | 0 | while ((ent = pgstat_entry_ref_hash_iterate(pgStatEntryRefHash, &i)) |
863 | 0 | != NULL) |
864 | 0 | { |
865 | 0 | Assert(ent->entry_ref != NULL); |
866 | |
|
867 | 0 | if (match && !match(ent, match_data)) |
868 | 0 | continue; |
869 | | |
870 | 0 | pgstat_release_entry_ref(ent->key, ent->entry_ref, discard_pending); |
871 | 0 | } |
872 | 0 | } |
873 | | |
874 | | /* |
875 | | * Release all local references to shared stats entries. |
876 | | * |
877 | | * When a process exits it cannot do so while still holding references onto |
878 | | * stats entries, otherwise the shared stats entries could never be freed. |
879 | | */ |
880 | | static void |
881 | | pgstat_release_all_entry_refs(bool discard_pending) |
882 | 0 | { |
883 | 0 | if (pgStatEntryRefHash == NULL) |
884 | 0 | return; |
885 | | |
886 | 0 | pgstat_release_matching_entry_refs(discard_pending, NULL, 0); |
887 | 0 | Assert(pgStatEntryRefHash->members == 0); |
888 | 0 | pgstat_entry_ref_hash_destroy(pgStatEntryRefHash); |
889 | 0 | pgStatEntryRefHash = NULL; |
890 | 0 | } |
891 | | |
892 | | static bool |
893 | | match_db(PgStat_EntryRefHashEntry *ent, Datum match_data) |
894 | 0 | { |
895 | 0 | Oid dboid = DatumGetObjectId(match_data); |
896 | |
|
897 | 0 | return ent->key.dboid == dboid; |
898 | 0 | } |
899 | | |
900 | | static void |
901 | | pgstat_release_db_entry_refs(Oid dboid) |
902 | 0 | { |
903 | 0 | pgstat_release_matching_entry_refs( /* discard pending = */ true, |
904 | 0 | match_db, |
905 | 0 | ObjectIdGetDatum(dboid)); |
906 | 0 | } |
907 | | |
908 | | |
909 | | /* ------------------------------------------------------------ |
910 | | * Dropping and resetting of stats entries |
911 | | * ------------------------------------------------------------ |
912 | | */ |
913 | | |
914 | | static void |
915 | | pgstat_free_entry(PgStatShared_HashEntry *shent, dshash_seq_status *hstat) |
916 | 0 | { |
917 | 0 | dsa_pointer pdsa; |
918 | 0 | PgStat_Kind kind = shent->key.kind; |
919 | | |
920 | | /* |
921 | | * Fetch dsa pointer before deleting entry - that way we can free the |
922 | | * memory after releasing the lock. |
923 | | */ |
924 | 0 | pdsa = shent->body; |
925 | |
|
926 | 0 | if (!hstat) |
927 | 0 | dshash_delete_entry(pgStatLocal.shared_hash, shent); |
928 | 0 | else |
929 | 0 | dshash_delete_current(hstat); |
930 | |
|
931 | 0 | dsa_free(pgStatLocal.dsa, pdsa); |
932 | | |
933 | | /* Decrement entry count, if required. */ |
934 | 0 | if (pgstat_get_kind_info(kind)->track_entry_count) |
935 | 0 | pg_atomic_sub_fetch_u64(&pgStatLocal.shmem->entry_counts[kind - 1], 1); |
936 | 0 | } |
937 | | |
938 | | /* |
939 | | * Helper for both pgstat_drop_database_and_contents() and |
940 | | * pgstat_drop_entry(). If hstat is non-null delete the shared entry using |
941 | | * dshash_delete_current(), otherwise use dshash_delete_entry(). In either |
942 | | * case the entry needs to be already locked. |
943 | | */ |
944 | | static bool |
945 | | pgstat_drop_entry_internal(PgStatShared_HashEntry *shent, |
946 | | dshash_seq_status *hstat) |
947 | 0 | { |
948 | 0 | Assert(shent->body != InvalidDsaPointer); |
949 | | |
950 | | /* should already have released local reference */ |
951 | 0 | if (pgStatEntryRefHash) |
952 | 0 | Assert(!pgstat_entry_ref_hash_lookup(pgStatEntryRefHash, shent->key)); |
953 | | |
954 | | /* |
955 | | * Signal that the entry is dropped - this will eventually cause other |
956 | | * backends to release their references. |
957 | | */ |
958 | 0 | Assert(!shent->dropped); |
959 | 0 | shent->dropped = true; |
960 | | |
961 | | /* release refcount marking entry as not dropped */ |
962 | 0 | if (pg_atomic_sub_fetch_u32(&shent->refcount, 1) == 0) |
963 | 0 | { |
964 | 0 | pgstat_free_entry(shent, hstat); |
965 | 0 | return true; |
966 | 0 | } |
967 | 0 | else |
968 | 0 | { |
969 | 0 | if (!hstat) |
970 | 0 | dshash_release_lock(pgStatLocal.shared_hash, shent); |
971 | 0 | return false; |
972 | 0 | } |
973 | 0 | } |
974 | | |
975 | | /* |
976 | | * Drop stats for the database and all the objects inside that database. |
977 | | */ |
978 | | static void |
979 | | pgstat_drop_database_and_contents(Oid dboid) |
980 | 0 | { |
981 | 0 | dshash_seq_status hstat; |
982 | 0 | PgStatShared_HashEntry *p; |
983 | 0 | uint64 not_freed_count = 0; |
984 | |
|
985 | 0 | Assert(OidIsValid(dboid)); |
986 | |
|
987 | 0 | Assert(pgStatLocal.shared_hash != NULL); |
988 | | |
989 | | /* |
990 | | * This backend might very well be the only backend holding a reference to |
991 | | * about-to-be-dropped entries. Ensure that we're not preventing it from |
992 | | * being cleaned up till later. |
993 | | * |
994 | | * Doing this separately from the dshash iteration below avoids having to |
995 | | * do so while holding a partition lock on the shared hashtable. |
996 | | */ |
997 | 0 | pgstat_release_db_entry_refs(dboid); |
998 | | |
999 | | /* some of the dshash entries are to be removed, take exclusive lock. */ |
1000 | 0 | dshash_seq_init(&hstat, pgStatLocal.shared_hash, true); |
1001 | 0 | while ((p = dshash_seq_next(&hstat)) != NULL) |
1002 | 0 | { |
1003 | 0 | if (p->dropped) |
1004 | 0 | continue; |
1005 | | |
1006 | 0 | if (p->key.dboid != dboid) |
1007 | 0 | continue; |
1008 | | |
1009 | 0 | if (!pgstat_drop_entry_internal(p, &hstat)) |
1010 | 0 | { |
1011 | | /* |
1012 | | * Even statistics for a dropped database might currently be |
1013 | | * accessed (consider e.g. database stats for pg_stat_database). |
1014 | | */ |
1015 | 0 | not_freed_count++; |
1016 | 0 | } |
1017 | 0 | } |
1018 | 0 | dshash_seq_term(&hstat); |
1019 | | |
1020 | | /* |
1021 | | * If some of the stats data could not be freed, signal the reference |
1022 | | * holders to run garbage collection of their cached pgStatLocal.shmem. |
1023 | | */ |
1024 | 0 | if (not_freed_count > 0) |
1025 | 0 | pgstat_request_entry_refs_gc(); |
1026 | 0 | } |
1027 | | |
1028 | | /* |
1029 | | * Drop a single stats entry. |
1030 | | * |
1031 | | * This routine returns false if the stats entry of the dropped object could |
1032 | | * not be freed, true otherwise. |
1033 | | * |
1034 | | * If missing_ok is true, skip entries that have been concurrently dropped. |
1035 | | * |
1036 | | * The callers of this function should call pgstat_request_entry_refs_gc() |
1037 | | * if the stats entry could not be freed, to ensure that this entry's memory |
1038 | | * can be reclaimed later by a different backend calling |
1039 | | * pgstat_gc_entry_refs(). |
1040 | | */ |
1041 | | bool |
1042 | | pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid, |
1043 | | bool missing_ok) |
1044 | 0 | { |
1045 | 0 | PgStat_HashKey key = {0}; |
1046 | 0 | PgStatShared_HashEntry *shent; |
1047 | 0 | bool freed = true; |
1048 | |
|
1049 | 0 | key.kind = kind; |
1050 | 0 | key.dboid = dboid; |
1051 | 0 | key.objid = objid; |
1052 | | |
1053 | | /* delete local reference */ |
1054 | 0 | if (pgStatEntryRefHash) |
1055 | 0 | { |
1056 | 0 | PgStat_EntryRefHashEntry *lohashent = |
1057 | 0 | pgstat_entry_ref_hash_lookup(pgStatEntryRefHash, key); |
1058 | |
|
1059 | 0 | if (lohashent) |
1060 | 0 | pgstat_release_entry_ref(lohashent->key, lohashent->entry_ref, |
1061 | 0 | true); |
1062 | 0 | } |
1063 | | |
1064 | | /* mark entry in shared hashtable as deleted, drop if possible */ |
1065 | 0 | shent = dshash_find(pgStatLocal.shared_hash, &key, true); |
1066 | 0 | if (shent) |
1067 | 0 | { |
1068 | 0 | if (shent->dropped) |
1069 | 0 | { |
1070 | 0 | if (!missing_ok) |
1071 | 0 | elog(ERROR, |
1072 | 0 | "trying to drop stats entry already dropped: kind=%s dboid=%u objid=%" PRIu64 " refcount=%u generation=%u", |
1073 | 0 | pgstat_get_kind_info(shent->key.kind)->name, |
1074 | 0 | shent->key.dboid, |
1075 | 0 | shent->key.objid, |
1076 | 0 | pg_atomic_read_u32(&shent->refcount), |
1077 | 0 | pg_atomic_read_u32(&shent->generation)); |
1078 | 0 | dshash_release_lock(pgStatLocal.shared_hash, shent); |
1079 | 0 | return true; |
1080 | 0 | } |
1081 | | |
1082 | 0 | freed = pgstat_drop_entry_internal(shent, NULL); |
1083 | | |
1084 | | /* |
1085 | | * Database stats contain other stats. Drop those as well when |
1086 | | * dropping the database. XXX: Perhaps this should be done in a |
1087 | | * slightly more principled way? But not obvious what that'd look |
1088 | | * like, and so far this is the only case... |
1089 | | */ |
1090 | 0 | if (key.kind == PGSTAT_KIND_DATABASE) |
1091 | 0 | pgstat_drop_database_and_contents(key.dboid); |
1092 | 0 | } |
1093 | | |
1094 | 0 | return freed; |
1095 | 0 | } |
1096 | | |
1097 | | /* |
1098 | | * Scan through the shared hashtable of stats, dropping statistics if |
1099 | | * approved by the optional do_drop() function. |
1100 | | */ |
1101 | | void |
1102 | | pgstat_drop_matching_entries(bool (*do_drop) (PgStatShared_HashEntry *, Datum), |
1103 | | Datum match_data) |
1104 | 0 | { |
1105 | 0 | dshash_seq_status hstat; |
1106 | 0 | PgStatShared_HashEntry *ps; |
1107 | 0 | uint64 not_freed_count = 0; |
1108 | | |
1109 | | /* entries are removed, take an exclusive lock */ |
1110 | 0 | dshash_seq_init(&hstat, pgStatLocal.shared_hash, true); |
1111 | 0 | while ((ps = dshash_seq_next(&hstat)) != NULL) |
1112 | 0 | { |
1113 | 0 | if (ps->dropped) |
1114 | 0 | continue; |
1115 | | |
1116 | 0 | if (do_drop != NULL && !do_drop(ps, match_data)) |
1117 | 0 | continue; |
1118 | | |
1119 | | /* delete local reference */ |
1120 | 0 | if (pgStatEntryRefHash) |
1121 | 0 | { |
1122 | 0 | PgStat_EntryRefHashEntry *lohashent = |
1123 | 0 | pgstat_entry_ref_hash_lookup(pgStatEntryRefHash, ps->key); |
1124 | |
|
1125 | 0 | if (lohashent) |
1126 | 0 | pgstat_release_entry_ref(lohashent->key, lohashent->entry_ref, |
1127 | 0 | true); |
1128 | 0 | } |
1129 | |
|
1130 | 0 | if (!pgstat_drop_entry_internal(ps, &hstat)) |
1131 | 0 | not_freed_count++; |
1132 | 0 | } |
1133 | 0 | dshash_seq_term(&hstat); |
1134 | |
|
1135 | 0 | if (not_freed_count > 0) |
1136 | 0 | pgstat_request_entry_refs_gc(); |
1137 | 0 | } |
1138 | | |
1139 | | /* |
1140 | | * Scan through the shared hashtable of stats and drop all entries. |
1141 | | */ |
1142 | | void |
1143 | | pgstat_drop_all_entries(void) |
1144 | 0 | { |
1145 | 0 | pgstat_drop_matching_entries(NULL, 0); |
1146 | 0 | } |
1147 | | |
1148 | | static void |
1149 | | shared_stat_reset_contents(PgStat_Kind kind, PgStatShared_Common *header, |
1150 | | TimestampTz ts) |
1151 | 0 | { |
1152 | 0 | const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind); |
1153 | |
|
1154 | 0 | memset(pgstat_get_entry_data(kind, header), 0, |
1155 | 0 | pgstat_get_entry_len(kind)); |
1156 | |
|
1157 | 0 | if (kind_info->reset_timestamp_cb) |
1158 | 0 | kind_info->reset_timestamp_cb(header, ts); |
1159 | 0 | } |
1160 | | |
1161 | | /* |
1162 | | * Reset one variable-numbered stats entry. |
1163 | | */ |
1164 | | void |
1165 | | pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts) |
1166 | 0 | { |
1167 | 0 | PgStat_EntryRef *entry_ref; |
1168 | |
|
1169 | 0 | Assert(!pgstat_get_kind_info(kind)->fixed_amount); |
1170 | |
|
1171 | 0 | entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL); |
1172 | 0 | if (!entry_ref || entry_ref->shared_entry->dropped) |
1173 | 0 | return; |
1174 | | |
1175 | 0 | (void) pgstat_lock_entry(entry_ref, false); |
1176 | 0 | shared_stat_reset_contents(kind, entry_ref->shared_stats, ts); |
1177 | 0 | pgstat_unlock_entry(entry_ref); |
1178 | 0 | } |
1179 | | |
1180 | | /* |
1181 | | * Scan through the shared hashtable of stats, resetting statistics if |
1182 | | * approved by the provided do_reset() function. |
1183 | | */ |
1184 | | void |
1185 | | pgstat_reset_matching_entries(bool (*do_reset) (PgStatShared_HashEntry *, Datum), |
1186 | | Datum match_data, TimestampTz ts) |
1187 | 0 | { |
1188 | 0 | dshash_seq_status hstat; |
1189 | 0 | PgStatShared_HashEntry *p; |
1190 | | |
1191 | | /* dshash entry is not modified, take shared lock */ |
1192 | 0 | dshash_seq_init(&hstat, pgStatLocal.shared_hash, false); |
1193 | 0 | while ((p = dshash_seq_next(&hstat)) != NULL) |
1194 | 0 | { |
1195 | 0 | PgStatShared_Common *header; |
1196 | |
|
1197 | 0 | if (p->dropped) |
1198 | 0 | continue; |
1199 | | |
1200 | 0 | if (!do_reset(p, match_data)) |
1201 | 0 | continue; |
1202 | | |
1203 | 0 | header = dsa_get_address(pgStatLocal.dsa, p->body); |
1204 | |
|
1205 | 0 | LWLockAcquire(&header->lock, LW_EXCLUSIVE); |
1206 | |
|
1207 | 0 | shared_stat_reset_contents(p->key.kind, header, ts); |
1208 | |
|
1209 | 0 | LWLockRelease(&header->lock); |
1210 | 0 | } |
1211 | 0 | dshash_seq_term(&hstat); |
1212 | 0 | } |
1213 | | |
1214 | | static bool |
1215 | | match_kind(PgStatShared_HashEntry *p, Datum match_data) |
1216 | 0 | { |
1217 | 0 | return p->key.kind == DatumGetInt32(match_data); |
1218 | 0 | } |
1219 | | |
1220 | | void |
1221 | | pgstat_reset_entries_of_kind(PgStat_Kind kind, TimestampTz ts) |
1222 | 0 | { |
1223 | 0 | pgstat_reset_matching_entries(match_kind, Int32GetDatum(kind), ts); |
1224 | 0 | } |
1225 | | |
1226 | | static void |
1227 | | pgstat_setup_memcxt(void) |
1228 | 0 | { |
1229 | 0 | if (unlikely(!pgStatSharedRefContext)) |
1230 | 0 | pgStatSharedRefContext = |
1231 | 0 | AllocSetContextCreate(TopMemoryContext, |
1232 | 0 | "PgStat Shared Ref", |
1233 | 0 | ALLOCSET_SMALL_SIZES); |
1234 | 0 | if (unlikely(!pgStatEntryRefHashContext)) |
1235 | 0 | pgStatEntryRefHashContext = |
1236 | 0 | AllocSetContextCreate(TopMemoryContext, |
1237 | 0 | "PgStat Shared Ref Hash", |
1238 | 0 | ALLOCSET_SMALL_SIZES); |
1239 | 0 | } |