Branch data Line data Source code
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 : : #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 : : #define SH_ELEMENT_TYPE PgStat_EntryRefHashEntry
36 : : #define SH_KEY_TYPE PgStat_HashKey
37 : : #define SH_KEY key
38 : : #define SH_HASH_KEY(tb, key) \
39 : : pgstat_hash_hash_key(&key, sizeof(PgStat_HashKey), NULL)
40 : : #define SH_EQUAL(tb, a, b) \
41 : : 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 : : /* Parameters for shared hashtables. */
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 a stats
109 : : * hashtable. 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 : 5308 : pgstat_dsa_init_size(void)
115 : : {
116 : : 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 : 5308 : sz = 256 * 1024;
127 : : Assert(dsa_minimum_size() <= sz);
128 : 5308 : return MAXALIGN(sz);
129 : : }
130 : :
131 : : /*
132 : : * Compute shared memory space needed for cumulative statistics
133 : : */
134 : : static Size
135 : 1327 : StatsShmemSize(void)
136 : : {
137 : : Size sz;
138 : :
139 : 1327 : sz = MAXALIGN(sizeof(PgStat_ShmemControl));
140 : 1327 : sz = add_size(sz, pgstat_dsa_init_size());
141 : :
142 : : /* Add per-kind DSA space for variable-numbered own_hash kinds. */
143 [ + + ]: 43791 : for (PgStat_Kind kind = PGSTAT_KIND_MIN; kind <= PGSTAT_KIND_MAX; kind++)
144 : : {
145 : 42464 : const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
146 : :
147 [ + + + + : 42464 : if (!kind_info || kind_info->fixed_amount || !kind_info->own_hash)
+ + ]
148 : 42461 : continue;
149 : :
150 : 3 : sz = add_size(sz, pgstat_dsa_init_size());
151 : : }
152 : :
153 : : /* Add shared memory for all the custom fixed-numbered statistics */
154 [ + + ]: 13270 : for (PgStat_Kind kind = PGSTAT_KIND_CUSTOM_MIN; kind <= PGSTAT_KIND_CUSTOM_MAX; kind++)
155 : : {
156 : 11943 : const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
157 : :
158 [ + + ]: 11943 : if (!kind_info)
159 : 11925 : continue;
160 [ + + ]: 18 : if (!kind_info->fixed_amount)
161 : 9 : continue;
162 : :
163 : : Assert(kind_info->shared_size != 0);
164 : 9 : sz = add_size(sz, MAXALIGN(kind_info->shared_size));
165 : : }
166 : :
167 : 1327 : return sz;
168 : : }
169 : :
170 : : /*
171 : : * Register shared memory area for cumulative statistics
172 : : */
173 : : static void
174 : 1327 : StatsShmemRequest(void *arg)
175 : : {
176 : 1327 : ShmemRequestStruct(.name = "Shared Memory Stats",
177 : : .size = StatsShmemSize(),
178 : : .ptr = (void **) &pgStatLocal.shmem,
179 : : );
180 : 1327 : }
181 : :
182 : : /*
183 : : * Initialize cumulative statistics system during startup
184 : : */
185 : : static void
186 : 1323 : StatsShmemInit(void *arg)
187 : : {
188 : : dsa_area *dsa;
189 : : dshash_table *dsh;
190 : 1323 : PgStat_ShmemControl *ctl = pgStatLocal.shmem;
191 : 1323 : char *p = (char *) ctl;
192 : :
193 : : /* the allocation of pgStatLocal.shmem itself */
194 : 1323 : p += MAXALIGN(sizeof(PgStat_ShmemControl));
195 : :
196 : : /*
197 : : * Create a small dsa allocation in plain shared memory. This is required
198 : : * because postmaster cannot use dsm segments. It also provides a small
199 : : * efficiency win.
200 : : */
201 : 1323 : ctl->raw_dsa_area = p;
202 : 1323 : p += pgstat_dsa_init_size();
203 : 1323 : dsa = dsa_create_in_place(ctl->raw_dsa_area,
204 : : pgstat_dsa_init_size(),
205 : : LWTRANCHE_PGSTATS_DSA, NULL);
206 : 1323 : dsa_pin(dsa);
207 : :
208 : : /*
209 : : * To ensure dshash is created in "plain" shared memory, temporarily limit
210 : : * size of dsa to the initial size of the dsa.
211 : : */
212 : 1323 : dsa_set_size_limit(dsa, pgstat_dsa_init_size());
213 : :
214 : : /*
215 : : * With the limit in place, create the dshash table. XXX: It'd be nice if
216 : : * there were dshash_create_in_place().
217 : : */
218 : 1323 : dsh = dshash_create(dsa, &dsh_params, NULL);
219 : 1323 : ctl->hash_handle = dshash_get_hash_table_handle(dsh);
220 : :
221 : : /* lift limit set above */
222 : 1323 : dsa_set_size_limit(dsa, -1);
223 : :
224 : : /*
225 : : * Create dedicated DSA areas and hash tables for kinds that have
226 : : * variable-numbered stats and set own_hash to true.
227 : : */
228 : 1323 : memset(ctl->raw_kind_dsa_area, 0, sizeof(ctl->raw_kind_dsa_area));
229 [ + + ]: 43659 : for (PgStat_Kind kind = PGSTAT_KIND_MIN; kind <= PGSTAT_KIND_MAX; kind++)
230 : : {
231 : 42336 : const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
232 : : dsa_area *kind_dsa;
233 : : dshash_table *kind_dsh;
234 : :
235 [ + + + + : 42336 : if (!kind_info || kind_info->fixed_amount || !kind_info->own_hash)
+ + ]
236 : 42333 : continue;
237 : :
238 : : /* Create a per-kind DSA in pre-allocated shared memory */
239 : 3 : ctl->raw_kind_dsa_area[kind] = p;
240 : 3 : p += pgstat_dsa_init_size();
241 : 3 : kind_dsa = dsa_create_in_place(ctl->raw_kind_dsa_area[kind],
242 : : pgstat_dsa_init_size(),
243 : : LWTRANCHE_PGSTATS_DSA, NULL);
244 : 3 : dsa_pin(kind_dsa);
245 : :
246 : : /* Temporarily limit to keep dshash in the in-place area */
247 : 3 : dsa_set_size_limit(kind_dsa, pgstat_dsa_init_size());
248 : :
249 : 3 : kind_dsh = dshash_create(kind_dsa, &dsh_params, NULL);
250 : 3 : ctl->kind_hash_handles[kind] = dshash_get_hash_table_handle(kind_dsh);
251 : :
252 : 3 : dsa_set_size_limit(kind_dsa, -1);
253 : :
254 : : /* detach, as postmaster never accesses them again. */
255 : 3 : dshash_detach(kind_dsh);
256 : 3 : dsa_detach(kind_dsa);
257 : : }
258 : :
259 : : /*
260 : : * Postmaster will never access these again, thus free the local
261 : : * dsa/dshash references.
262 : : */
263 : 1323 : dshash_detach(dsh);
264 : 1323 : dsa_detach(dsa);
265 : :
266 : 1323 : pg_atomic_init_u64(&ctl->gc_request_count, 1);
267 : :
268 : : /* Do the per-kind initialization */
269 [ + + ]: 43659 : for (PgStat_Kind kind = PGSTAT_KIND_MIN; kind <= PGSTAT_KIND_MAX; kind++)
270 : : {
271 : 42336 : const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
272 : : char *ptr;
273 : :
274 [ + + ]: 42336 : if (!kind_info)
275 : 23796 : continue;
276 : :
277 : : /* initialize entry count tracking */
278 [ + + ]: 18540 : if (kind_info->track_entry_count)
279 : 9 : pg_atomic_init_u64(&ctl->entry_counts[kind - 1], 0);
280 : :
281 : : /* initialize fixed-numbered stats */
282 [ + + ]: 18540 : if (kind_info->fixed_amount)
283 : : {
284 [ + + ]: 9270 : if (pgstat_is_kind_builtin(kind))
285 : 9261 : ptr = ((char *) ctl) + kind_info->shared_ctl_off;
286 : : else
287 : : {
288 : 9 : int idx = kind - PGSTAT_KIND_CUSTOM_MIN;
289 : :
290 : : Assert(kind_info->shared_size != 0);
291 : 9 : ctl->custom_data[idx] = p;
292 : 9 : p += MAXALIGN(kind_info->shared_size);
293 : 9 : ptr = ctl->custom_data[idx];
294 : : }
295 : :
296 : 9270 : kind_info->init_shmem_cb(ptr);
297 : : }
298 : : }
299 : 1323 : }
300 : :
301 : : void
302 : 26040 : pgstat_attach_shmem(void)
303 : : {
304 : : dsa_area *shared_dsa;
305 : : dshash_table *shared_hash;
306 : : MemoryContext oldcontext;
307 : :
308 : : Assert(pgStatLocal.num_var_hashes == 0);
309 : :
310 : : /* stats shared memory persists for the backend lifetime */
311 : 26040 : oldcontext = MemoryContextSwitchTo(TopMemoryContext);
312 : :
313 : 26040 : shared_dsa = dsa_attach_in_place(pgStatLocal.shmem->raw_dsa_area,
314 : : NULL);
315 : 26040 : dsa_pin_mapping(shared_dsa);
316 : :
317 : 26040 : shared_hash = dshash_attach(shared_dsa, &dsh_params,
318 : 26040 : pgStatLocal.shmem->hash_handle,
319 : : NULL);
320 : :
321 : : /* Build per-kind mappings for the hash tables. */
322 : 26040 : pgStatLocal.var_hashes[pgStatLocal.num_var_hashes++] = shared_hash;
323 : :
324 [ + + ]: 859320 : for (PgStat_Kind kind = PGSTAT_KIND_MIN; kind <= PGSTAT_KIND_MAX; kind++)
325 : : {
326 : 833280 : const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
327 : :
328 [ + + ]: 833280 : if (pgStatLocal.shmem->raw_kind_dsa_area[kind] != NULL)
329 : : {
330 : : dsa_area *kind_dsa;
331 : :
332 : : /* per-kind hashtable */
333 : : kind_dsa =
334 : 38 : dsa_attach_in_place(pgStatLocal.shmem->raw_kind_dsa_area[kind],
335 : : NULL);
336 : 38 : dsa_pin_mapping(kind_dsa);
337 : :
338 : 38 : pgStatLocal.kind_hash[kind] =
339 : 38 : dshash_attach(kind_dsa, &dsh_params,
340 : 38 : pgStatLocal.shmem->kind_hash_handles[kind],
341 : : NULL);
342 : 38 : pgStatLocal.kind_dsa[kind] = kind_dsa;
343 : 38 : pgStatLocal.var_hashes[pgStatLocal.num_var_hashes++] =
344 : 38 : pgStatLocal.kind_hash[kind];
345 : : }
346 [ + + + + ]: 833242 : else if (kind_info && !kind_info->fixed_amount)
347 : : {
348 : : /* main shared hashtable */
349 : 182342 : pgStatLocal.kind_hash[kind] = shared_hash;
350 : 182342 : pgStatLocal.kind_dsa[kind] = shared_dsa;
351 : : }
352 : : else
353 : : {
354 : : /* unassigned kind ID */
355 : 650900 : pgStatLocal.kind_hash[kind] = NULL;
356 : 650900 : pgStatLocal.kind_dsa[kind] = NULL;
357 : : }
358 : : }
359 : :
360 : 26040 : MemoryContextSwitchTo(oldcontext);
361 : 26040 : }
362 : :
363 : : void
364 : 26040 : pgstat_detach_shmem(void)
365 : : {
366 : : Assert(pgStatLocal.num_var_hashes > 0);
367 : :
368 : : /* we shouldn't leave references to shared stats */
369 : 26040 : pgstat_release_all_entry_refs(false);
370 : :
371 [ + + ]: 52118 : for (int h = 0; h < pgStatLocal.num_var_hashes; h++)
372 : : {
373 : 26078 : dsa_area *dsa = dshash_get_dsa_area(pgStatLocal.var_hashes[h]);
374 : :
375 : 26078 : dshash_detach(pgStatLocal.var_hashes[h]);
376 : 26078 : dsa_detach(dsa);
377 : 26078 : pgStatLocal.var_hashes[h] = NULL;
378 : : }
379 : :
380 : 26040 : pgStatLocal.num_var_hashes = 0;
381 : :
382 : : /* Reset per-kind mappings. */
383 [ + + ]: 859320 : for (PgStat_Kind kind = PGSTAT_KIND_MIN; kind <= PGSTAT_KIND_MAX; kind++)
384 : : {
385 : 833280 : pgStatLocal.kind_hash[kind] = NULL;
386 : 833280 : pgStatLocal.kind_dsa[kind] = NULL;
387 : : }
388 : :
389 : : /*
390 : : * dsa_detach() does not decrement the DSA reference count as no segment
391 : : * was provided to dsa_attach_in_place(), causing no cleanup callbacks to
392 : : * be registered. Hence, release it manually now.
393 : : */
394 : 26040 : dsa_release_in_place(pgStatLocal.shmem->raw_dsa_area);
395 : :
396 [ + + ]: 859320 : for (PgStat_Kind kind = PGSTAT_KIND_MIN; kind <= PGSTAT_KIND_MAX; kind++)
397 : : {
398 [ + + ]: 833280 : if (pgStatLocal.shmem->raw_kind_dsa_area[kind] != NULL)
399 : 38 : dsa_release_in_place(pgStatLocal.shmem->raw_kind_dsa_area[kind]);
400 : : }
401 : 26040 : }
402 : :
403 : :
404 : : /* ------------------------------------------------------------
405 : : * Maintenance of shared memory stats entries
406 : : * ------------------------------------------------------------
407 : : */
408 : :
409 : : /*
410 : : * Allocate the DSA body for a new variable-numbered pgstats entry.
411 : : *
412 : : * Returns InvalidDsaPointer in the event of an allocation failure.
413 : : */
414 : : dsa_pointer
415 : 392873 : pgstat_alloc_entry_body(PgStat_Kind kind)
416 : : {
417 : 392873 : const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
418 : :
419 : 785746 : return dsa_allocate_extended(pgStatLocal.kind_dsa[kind],
420 : 392873 : kind_info->shared_size,
421 : : DSA_ALLOC_ZERO | DSA_ALLOC_NO_OOM);
422 : : }
423 : :
424 : : /*
425 : : * Initialize variable-numbered pgstats entry.
426 : : *
427 : : * "chunk" must be a valid pointer, allocated previously by
428 : : * pgstat_alloc_entry_body().
429 : : */
430 : : PgStatShared_Common *
431 : 392873 : pgstat_init_entry(PgStat_Kind kind,
432 : : PgStatShared_HashEntry *shhashent,
433 : : dsa_pointer chunk)
434 : : {
435 : : PgStatShared_Common *shheader;
436 : 392873 : const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
437 : :
438 : : Assert(DsaPointerIsValid(chunk));
439 : :
440 : : /*
441 : : * Initialize refcount to 1, marking it as valid / not dropped. The entry
442 : : * can't be freed before the initialization because it can't be found as
443 : : * long as we hold the dshash partition lock. Caller needs to increase
444 : : * further if a longer lived reference is needed.
445 : : */
446 : 392873 : pg_atomic_init_u32(&shhashent->refcount, 1);
447 : :
448 : : /*
449 : : * Initialize "generation" to 0, as freshly created.
450 : : */
451 : 392873 : pg_atomic_init_u32(&shhashent->generation, 0);
452 : 392873 : shhashent->dropped = false;
453 : :
454 : 392873 : shheader = dsa_get_address(pgStatLocal.kind_dsa[kind], chunk);
455 : 392873 : shheader->magic = 0xdeadbeef;
456 : :
457 : : /* Link the new entry from the hash entry. */
458 : 392873 : shhashent->body = chunk;
459 : :
460 : : /* Increment entry count, if required. */
461 [ + + ]: 392873 : if (kind_info->track_entry_count)
462 : 12 : pg_atomic_fetch_add_u64(&pgStatLocal.shmem->entry_counts[kind - 1], 1);
463 : :
464 : 392873 : LWLockInitialize(&shheader->lock, LWTRANCHE_PGSTATS_DATA);
465 : :
466 : 392873 : return shheader;
467 : : }
468 : :
469 : : static PgStatShared_Common *
470 : 30 : pgstat_reinit_entry(PgStat_Kind kind, PgStatShared_HashEntry *shhashent)
471 : : {
472 : : PgStatShared_Common *shheader;
473 : :
474 : 30 : shheader = dsa_get_address(pgStatLocal.kind_dsa[kind],
475 : : shhashent->body);
476 : :
477 : : /* mark as not dropped anymore */
478 : 30 : pg_atomic_fetch_add_u32(&shhashent->refcount, 1);
479 : :
480 : : /*
481 : : * Increment "generation", to let any backend with local references know
482 : : * that what they point to is outdated.
483 : : */
484 : 30 : pg_atomic_fetch_add_u32(&shhashent->generation, 1);
485 : 30 : shhashent->dropped = false;
486 : :
487 : : /* reinitialize content */
488 : : Assert(shheader->magic == 0xdeadbeef);
489 : 30 : memset(pgstat_get_entry_data(kind, shheader), 0,
490 : : pgstat_get_entry_len(kind));
491 : :
492 : 30 : return shheader;
493 : : }
494 : :
495 : : static void
496 : 4551840 : pgstat_setup_shared_refs(void)
497 : : {
498 [ + + ]: 4551840 : if (likely(pgStatEntryRefHash != NULL))
499 : 4529509 : return;
500 : :
501 : 22331 : pgStatEntryRefHash =
502 : 22331 : pgstat_entry_ref_hash_create(pgStatEntryRefHashContext,
503 : : PGSTAT_ENTRY_REF_HASH_SIZE, NULL);
504 : 22331 : pgStatSharedRefAge = pg_atomic_read_u64(&pgStatLocal.shmem->gc_request_count);
505 : : Assert(pgStatSharedRefAge != 0);
506 : : }
507 : :
508 : : /*
509 : : * Helper function for pgstat_get_entry_ref().
510 : : */
511 : : static void
512 : 1188330 : pgstat_acquire_entry_ref(PgStat_EntryRef *entry_ref,
513 : : dshash_table *hash,
514 : : PgStatShared_HashEntry *shhashent,
515 : : PgStatShared_Common *shheader)
516 : : {
517 : : Assert(shheader->magic == 0xdeadbeef);
518 : : Assert(pg_atomic_read_u32(&shhashent->refcount) > 0);
519 : :
520 : 1188330 : pg_atomic_fetch_add_u32(&shhashent->refcount, 1);
521 : :
522 : 1188330 : entry_ref->shared_stats = shheader;
523 : 1188330 : entry_ref->shared_entry = shhashent;
524 : 1188330 : entry_ref->generation = pg_atomic_read_u32(&shhashent->generation);
525 : :
526 : : /*
527 : : * Complete the local reference before releasing the lock. Releasing an
528 : : * LWLock can process a pending interrupt, and callers may catch the
529 : : * resulting error and continue using the backend-local cache.
530 : : */
531 : 1188330 : dshash_release_lock(hash, shhashent);
532 : 1188330 : }
533 : :
534 : : /*
535 : : * Helper function for pgstat_get_entry_ref().
536 : : */
537 : : static bool
538 : 4551840 : pgstat_get_entry_ref_cached(PgStat_HashKey key, PgStat_EntryRef **entry_ref_p)
539 : : {
540 : : bool found;
541 : : PgStat_EntryRefHashEntry *cache_entry;
542 : :
543 : : /*
544 : : * We immediately insert a cache entry, because it avoids 1) multiple
545 : : * hashtable lookups in case of a cache miss 2) having to deal with
546 : : * out-of-memory errors after incrementing PgStatShared_Common->refcount.
547 : : */
548 : :
549 : 4551840 : cache_entry = pgstat_entry_ref_hash_insert(pgStatEntryRefHash, key, &found);
550 : :
551 [ + + - + ]: 4551840 : if (!found || !cache_entry->entry_ref)
552 : 1294404 : {
553 : : PgStat_EntryRef *entry_ref;
554 : :
555 : 1294404 : entry_ref = MemoryContextAllocExtended(pgStatSharedRefContext,
556 : : sizeof(PgStat_EntryRef),
557 : : MCXT_ALLOC_NO_OOM);
558 [ - + ]: 1294404 : if (unlikely(entry_ref == NULL))
559 : : {
560 : : /*
561 : : * Clean the hash entry to keep the table consistent in the
562 : : * backend.
563 : : */
564 : 0 : pgstat_entry_ref_hash_delete(pgStatEntryRefHash, key);
565 : :
566 [ # # ]: 0 : ereport(ERROR,
567 : : (errcode(ERRCODE_OUT_OF_MEMORY),
568 : : errmsg("out of memory")));
569 : : }
570 : :
571 : 1294404 : cache_entry->entry_ref = entry_ref;
572 : 1294404 : entry_ref->shared_stats = NULL;
573 : 1294404 : entry_ref->shared_entry = NULL;
574 : 1294404 : entry_ref->pending = NULL;
575 : :
576 : 1294404 : found = false;
577 : : }
578 [ - + ]: 3257436 : else if (cache_entry->entry_ref->shared_stats == NULL)
579 : : {
580 : : Assert(cache_entry->entry_ref->pending == NULL);
581 : 0 : found = false;
582 : : }
583 : : else
584 : : {
585 : : PgStat_EntryRef *entry_ref PG_USED_FOR_ASSERTS_ONLY;
586 : :
587 : 3257436 : entry_ref = cache_entry->entry_ref;
588 : : Assert(entry_ref->shared_entry != NULL);
589 : : Assert(entry_ref->shared_stats != NULL);
590 : :
591 : : Assert(entry_ref->shared_stats->magic == 0xdeadbeef);
592 : : /* should have at least our reference */
593 : : Assert(pg_atomic_read_u32(&entry_ref->shared_entry->refcount) > 0);
594 : : }
595 : :
596 : 4551840 : *entry_ref_p = cache_entry->entry_ref;
597 : 4551840 : return found;
598 : : }
599 : :
600 : : /*
601 : : * Get a shared stats reference. If create is true, the shared stats object is
602 : : * created if it does not exist.
603 : : *
604 : : * When create is true, and created_entry is non-NULL, it'll be set to true
605 : : * if the entry is newly created, false otherwise.
606 : : */
607 : : PgStat_EntryRef *
608 : 4551840 : pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
609 : : bool *created_entry)
610 : : {
611 : 4551840 : PgStat_HashKey key = {0};
612 : : PgStatShared_HashEntry *shhashent;
613 : 4551840 : PgStatShared_Common *shheader = NULL;
614 : : PgStat_EntryRef *entry_ref;
615 : : dshash_table *hash;
616 : : dsa_area *dsa;
617 : :
618 : 4551840 : key.kind = kind;
619 : 4551840 : key.dboid = dboid;
620 : 4551840 : key.objid = objid;
621 : :
622 : : /*
623 : : * passing in created_entry only makes sense if we possibly could create
624 : : * entry.
625 : : */
626 : : Assert(create || created_entry == NULL);
627 : : pgstat_assert_is_up();
628 : : Assert(pgStatLocal.num_var_hashes > 0);
629 : : Assert(!pgStatLocal.shmem->is_shutdown);
630 : :
631 : 4551840 : pgstat_setup_memcxt();
632 : 4551840 : pgstat_setup_shared_refs();
633 : :
634 [ + + ]: 4551840 : if (created_entry != NULL)
635 : 116 : *created_entry = false;
636 : :
637 : : /*
638 : : * Check if other backends dropped stats that could not be deleted because
639 : : * somebody held references to it. If so, check this backend's references.
640 : : * This is not expected to happen often. The location of the check is a
641 : : * bit random, but this is a relatively frequently called path, so better
642 : : * than most.
643 : : */
644 [ + + ]: 4551840 : if (pgstat_need_entry_refs_gc())
645 : 6437 : pgstat_gc_entry_refs();
646 : :
647 : : /*
648 : : * First check the lookup cache hashtable in local memory. If we find a
649 : : * match here we can avoid taking locks / causing contention.
650 : : */
651 [ + + ]: 4551840 : if (pgstat_get_entry_ref_cached(key, &entry_ref))
652 : 3257436 : return entry_ref;
653 : :
654 : : Assert(entry_ref != NULL);
655 : :
656 : : /* Per-kind hashtable and DSA area */
657 : 1294404 : hash = pgStatLocal.kind_hash[kind];
658 : 1294404 : dsa = pgStatLocal.kind_dsa[kind];
659 : :
660 : : /*
661 : : * Do a lookup in the hash table first - it's quite likely that the entry
662 : : * already exists, and that way we only need a shared lock.
663 : : */
664 : 1294404 : shhashent = dshash_find(hash, &key, false);
665 : :
666 [ + + + + ]: 1294404 : if (create && !shhashent)
667 : : {
668 : : bool shfound;
669 : : dsa_pointer chunk;
670 : :
671 : : /* Allocate the stats body before inserting a hash entry. */
672 : 147995 : chunk = pgstat_alloc_entry_body(kind);
673 [ - + ]: 147995 : if (chunk == InvalidDsaPointer)
674 : : {
675 : 0 : pgstat_release_entry_ref(key, entry_ref, false);
676 [ # # ]: 0 : ereport(ERROR,
677 : : (errcode(ERRCODE_OUT_OF_MEMORY),
678 : : errmsg("out of memory"),
679 : : errdetail("Failed while allocating entry %u/%u/%" PRIu64 ".",
680 : : key.kind, key.dboid, key.objid)));
681 : : }
682 : :
683 : : /*
684 : : * It's possible that somebody created the entry since the above
685 : : * lookup. If so, fall through to the same path as if we'd have if it
686 : : * already had been created before the dshash_find() calls.
687 : : */
688 : 147995 : shhashent = dshash_find_or_insert_extended(hash,
689 : : &key, &shfound,
690 : : DSHASH_INSERT_NO_OOM);
691 [ - + ]: 147995 : if (!shhashent)
692 : : {
693 : 0 : dsa_free(dsa, chunk);
694 : :
695 : : /*
696 : : * Clean up the local reference when failing insert into the stats
697 : : * hashtable.
698 : : */
699 : 0 : pgstat_release_entry_ref(key, entry_ref, false);
700 [ # # ]: 0 : ereport(ERROR,
701 : : (errcode(ERRCODE_OUT_OF_MEMORY),
702 : : errmsg("out of memory"),
703 : : errdetail("Failed while inserting entry %u/%u/%" PRIu64 ".",
704 : : key.kind, key.dboid, key.objid)));
705 : : }
706 : :
707 [ + - ]: 147995 : if (!shfound)
708 : : {
709 : 147995 : shheader = pgstat_init_entry(kind, shhashent, chunk);
710 : 147995 : pgstat_acquire_entry_ref(entry_ref, hash, shhashent, shheader);
711 : :
712 [ + + ]: 147995 : if (created_entry != NULL)
713 : 51 : *created_entry = true;
714 : :
715 : 147995 : return entry_ref;
716 : : }
717 : :
718 : : /* Concurrent insert won; drop the unused body. */
719 : 0 : dsa_free(dsa, chunk);
720 : : }
721 : :
722 [ + + ]: 1146409 : if (!shhashent)
723 : : {
724 : : /*
725 : : * If we're not creating, delete the reference again. In all
726 : : * likelihood it's just a stats lookup - no point wasting memory for a
727 : : * shared ref to nothing...
728 : : */
729 : 106037 : pgstat_release_entry_ref(key, entry_ref, false);
730 : :
731 : 106037 : return NULL;
732 : : }
733 : : else
734 : : {
735 : : /*
736 : : * Can get here either because dshash_find() found a match, or if
737 : : * dshash_find_or_insert() found a concurrently inserted entry.
738 : : */
739 : :
740 [ + + + + ]: 1040372 : if (shhashent->dropped && create)
741 : : {
742 : : /*
743 : : * There are legitimate cases where the old stats entry might not
744 : : * yet have been dropped by the time it's reused. The most obvious
745 : : * case are replication slot stats, where a new slot can be
746 : : * created with the same index just after dropping. But oid
747 : : * wraparound can lead to other cases as well. We just reset the
748 : : * stats to their plain state, while incrementing its "generation"
749 : : * in the shared entry for any remaining local references.
750 : : */
751 : 30 : shheader = pgstat_reinit_entry(kind, shhashent);
752 : 30 : pgstat_acquire_entry_ref(entry_ref, hash, shhashent, shheader);
753 : :
754 [ - + ]: 30 : if (created_entry != NULL)
755 : 0 : *created_entry = true;
756 : :
757 : 30 : return entry_ref;
758 : : }
759 [ + + ]: 1040342 : else if (shhashent->dropped)
760 : : {
761 : 37 : dshash_release_lock(hash, shhashent);
762 : 37 : pgstat_release_entry_ref(key, entry_ref, false);
763 : :
764 : 37 : return NULL;
765 : : }
766 : : else
767 : : {
768 : 1040305 : shheader = dsa_get_address(dsa, shhashent->body);
769 : 1040305 : pgstat_acquire_entry_ref(entry_ref, hash, shhashent, shheader);
770 : :
771 : 1040305 : return entry_ref;
772 : : }
773 : : }
774 : : }
775 : :
776 : : static void
777 : 1294404 : pgstat_release_entry_ref(PgStat_HashKey key, PgStat_EntryRef *entry_ref,
778 : : bool discard_pending)
779 : : {
780 [ + - + + ]: 1294404 : if (entry_ref && entry_ref->pending)
781 : : {
782 [ + - ]: 44114 : if (discard_pending)
783 : 44114 : pgstat_delete_pending_entry(entry_ref);
784 : : else
785 [ # # ]: 0 : elog(ERROR, "releasing ref with pending data");
786 : : }
787 : :
788 [ + - + + ]: 1294404 : if (entry_ref && entry_ref->shared_stats)
789 : : {
790 : : Assert(entry_ref->shared_stats->magic == 0xdeadbeef);
791 : : Assert(entry_ref->pending == NULL);
792 : :
793 : : /*
794 : : * This can't race with another backend looking up the stats entry and
795 : : * increasing the refcount because it is not "legal" to create
796 : : * additional references to dropped entries.
797 : : */
798 [ + + ]: 1188330 : if (pg_atomic_fetch_sub_u32(&entry_ref->shared_entry->refcount, 1) == 1)
799 : : {
800 : : PgStatShared_HashEntry *shent;
801 : :
802 : : /*
803 : : * We're the last referrer to this entry, try to drop the shared
804 : : * entry.
805 : : */
806 : :
807 : : /* only dropped entries can reach a 0 refcount */
808 : : Assert(entry_ref->shared_entry->dropped);
809 : :
810 : 5135 : shent = dshash_find(pgStatLocal.kind_hash[key.kind],
811 : 5135 : &entry_ref->shared_entry->key, true);
812 [ - + ]: 5135 : if (!shent)
813 [ # # ]: 0 : elog(ERROR, "could not find just referenced shared stats entry");
814 : :
815 : : /*
816 : : * This entry may have been reinitialized while trying to release
817 : : * it, so double-check that it has not been reused while holding a
818 : : * lock on its shared entry.
819 : : */
820 : 5135 : if (pg_atomic_read_u32(&entry_ref->shared_entry->generation) ==
821 [ + - ]: 5135 : entry_ref->generation)
822 : : {
823 : : /* Same "generation", so we're OK with the removal */
824 : : Assert(pg_atomic_read_u32(&entry_ref->shared_entry->refcount) == 0);
825 : : Assert(entry_ref->shared_entry == shent);
826 : 5135 : pgstat_free_entry(shent, NULL);
827 : : }
828 : : else
829 : : {
830 : : /*
831 : : * Shared stats entry has been reinitialized, so do not drop
832 : : * its shared entry, only release its lock.
833 : : */
834 : 0 : dshash_release_lock(pgStatLocal.kind_hash[key.kind], shent);
835 : : }
836 : : }
837 : : }
838 : :
839 [ - + ]: 1294404 : if (!pgstat_entry_ref_hash_delete(pgStatEntryRefHash, key))
840 [ # # ]: 0 : elog(ERROR, "entry ref vanished before deletion");
841 : :
842 [ + - ]: 1294404 : if (entry_ref)
843 : 1294404 : pfree(entry_ref);
844 : 1294404 : }
845 : :
846 : : /*
847 : : * Acquire exclusive lock on the entry.
848 : : *
849 : : * If nowait is true, it's just a conditional acquire, and the result
850 : : * *must* be checked to verify success.
851 : : * If nowait is false, waits as necessary, always returning true.
852 : : */
853 : : bool
854 : 1537828 : pgstat_lock_entry(PgStat_EntryRef *entry_ref, bool nowait)
855 : : {
856 : 1537828 : LWLock *lock = &entry_ref->shared_stats->lock;
857 : :
858 [ + + ]: 1537828 : if (nowait)
859 : 424077 : return LWLockConditionalAcquire(lock, LW_EXCLUSIVE);
860 : :
861 : 1113751 : LWLockAcquire(lock, LW_EXCLUSIVE);
862 : 1113751 : return true;
863 : : }
864 : :
865 : : /*
866 : : * Acquire shared lock on the entry.
867 : : *
868 : : * Separate from pgstat_lock_entry() as most callers will need to lock
869 : : * exclusively. The wait semantics are identical.
870 : : */
871 : : bool
872 : 296361 : pgstat_lock_entry_shared(PgStat_EntryRef *entry_ref, bool nowait)
873 : : {
874 : 296361 : LWLock *lock = &entry_ref->shared_stats->lock;
875 : :
876 [ - + ]: 296361 : if (nowait)
877 : 0 : return LWLockConditionalAcquire(lock, LW_SHARED);
878 : :
879 : 296361 : LWLockAcquire(lock, LW_SHARED);
880 : 296361 : return true;
881 : : }
882 : :
883 : : void
884 : 1834189 : pgstat_unlock_entry(PgStat_EntryRef *entry_ref)
885 : : {
886 : 1834189 : LWLockRelease(&entry_ref->shared_stats->lock);
887 : 1834189 : }
888 : :
889 : : /*
890 : : * Helper function to fetch and lock shared stats.
891 : : */
892 : : PgStat_EntryRef *
893 : 329096 : pgstat_get_entry_ref_locked(PgStat_Kind kind, Oid dboid, uint64 objid,
894 : : bool nowait)
895 : : {
896 : : PgStat_EntryRef *entry_ref;
897 : :
898 : : /* find shared table stats entry corresponding to the local entry */
899 : 329096 : entry_ref = pgstat_get_entry_ref(kind, dboid, objid, true, NULL);
900 : :
901 : : /* lock the shared entry to protect the content, skip if failed */
902 [ - + ]: 329096 : if (!pgstat_lock_entry(entry_ref, nowait))
903 : 0 : return NULL;
904 : :
905 : 329096 : return entry_ref;
906 : : }
907 : :
908 : : void
909 : 2029 : pgstat_request_entry_refs_gc(void)
910 : : {
911 : 2029 : pg_atomic_fetch_add_u64(&pgStatLocal.shmem->gc_request_count, 1);
912 : 2029 : }
913 : :
914 : : static bool
915 : 4551840 : pgstat_need_entry_refs_gc(void)
916 : : {
917 : : uint64 curage;
918 : :
919 [ - + ]: 4551840 : if (!pgStatEntryRefHash)
920 : 0 : return false;
921 : :
922 : : /* should have been initialized when creating pgStatEntryRefHash */
923 : : Assert(pgStatSharedRefAge != 0);
924 : :
925 : 4551840 : curage = pg_atomic_read_u64(&pgStatLocal.shmem->gc_request_count);
926 : :
927 : 4551840 : return pgStatSharedRefAge != curage;
928 : : }
929 : :
930 : : static void
931 : 6437 : pgstat_gc_entry_refs(void)
932 : : {
933 : : pgstat_entry_ref_hash_iterator i;
934 : : PgStat_EntryRefHashEntry *ent;
935 : : uint64 curage;
936 : :
937 : 6437 : curage = pg_atomic_read_u64(&pgStatLocal.shmem->gc_request_count);
938 : : Assert(curage != 0);
939 : :
940 : : /*
941 : : * Some entries have been dropped or reinitialized. Invalidate cache
942 : : * pointer to them.
943 : : */
944 : 6437 : pgstat_entry_ref_hash_start_iterate(pgStatEntryRefHash, &i);
945 [ + + ]: 482072 : while ((ent = pgstat_entry_ref_hash_iterate(pgStatEntryRefHash, &i)) != NULL)
946 : : {
947 : 475635 : PgStat_EntryRef *entry_ref = ent->entry_ref;
948 : :
949 : : Assert(!entry_ref->shared_stats ||
950 : : entry_ref->shared_stats->magic == 0xdeadbeef);
951 : :
952 : : /* A NULL shared_entry marks a partial reference. */
953 [ - + ]: 475635 : if (entry_ref->shared_entry == NULL)
954 : : {
955 : : Assert(entry_ref->shared_stats == NULL);
956 : : Assert(entry_ref->pending == NULL);
957 : 0 : pgstat_release_entry_ref(ent->key, entry_ref, false);
958 : 0 : continue;
959 : : }
960 : :
961 : : /*
962 : : * "generation" checks for the case of entries being reinitialized,
963 : : * and "dropped" for the case where these are.. dropped.
964 : : */
965 [ + + ]: 475635 : if (!entry_ref->shared_entry->dropped &&
966 : 346684 : pg_atomic_read_u32(&entry_ref->shared_entry->generation) ==
967 [ + + ]: 346684 : entry_ref->generation)
968 : 346654 : continue;
969 : :
970 : : /* cannot gc shared ref that has pending data */
971 [ + + ]: 128981 : if (entry_ref->pending != NULL)
972 : 124008 : continue;
973 : :
974 : 4973 : pgstat_release_entry_ref(ent->key, entry_ref, false);
975 : : }
976 : :
977 : 6437 : pgStatSharedRefAge = curage;
978 : 6437 : }
979 : :
980 : : static void
981 : 22378 : pgstat_release_matching_entry_refs(bool discard_pending, ReleaseMatchCB match,
982 : : Datum match_data)
983 : : {
984 : : pgstat_entry_ref_hash_iterator i;
985 : : PgStat_EntryRefHashEntry *ent;
986 : :
987 [ + + ]: 22378 : if (pgStatEntryRefHash == NULL)
988 : 1 : return;
989 : :
990 : 22377 : pgstat_entry_ref_hash_start_iterate(pgStatEntryRefHash, &i);
991 : :
992 : 1140776 : while ((ent = pgstat_entry_ref_hash_iterate(pgStatEntryRefHash, &i))
993 [ + + ]: 1140776 : != NULL)
994 : : {
995 : : Assert(ent->entry_ref != NULL);
996 : :
997 [ + + + - ]: 1118399 : if (match && !match(ent, match_data))
998 : 1403 : continue;
999 : :
1000 : 1116996 : pgstat_release_entry_ref(ent->key, ent->entry_ref, discard_pending);
1001 : : }
1002 : : }
1003 : :
1004 : : /*
1005 : : * Release all local references to shared stats entries.
1006 : : *
1007 : : * When a process exits it cannot do so while still holding references onto
1008 : : * stats entries, otherwise the shared stats entries could never be freed.
1009 : : */
1010 : : static void
1011 : 26040 : pgstat_release_all_entry_refs(bool discard_pending)
1012 : : {
1013 [ + + ]: 26040 : if (pgStatEntryRefHash == NULL)
1014 : 3709 : return;
1015 : :
1016 : 22331 : pgstat_release_matching_entry_refs(discard_pending, NULL, 0);
1017 : : Assert(pgStatEntryRefHash->members == 0);
1018 : 22331 : pgstat_entry_ref_hash_destroy(pgStatEntryRefHash);
1019 : 22331 : pgStatEntryRefHash = NULL;
1020 : : }
1021 : :
1022 : : static bool
1023 : 1403 : match_db(PgStat_EntryRefHashEntry *ent, Datum match_data)
1024 : : {
1025 : 1403 : Oid dboid = DatumGetObjectId(match_data);
1026 : :
1027 : 1403 : return ent->key.dboid == dboid;
1028 : : }
1029 : :
1030 : : static void
1031 : 47 : pgstat_release_db_entry_refs(Oid dboid)
1032 : : {
1033 : 47 : pgstat_release_matching_entry_refs( /* discard pending = */ true,
1034 : : match_db,
1035 : : ObjectIdGetDatum(dboid));
1036 : 47 : }
1037 : :
1038 : :
1039 : : /* ------------------------------------------------------------
1040 : : * Dropping and resetting of stats entries
1041 : : * ------------------------------------------------------------
1042 : : */
1043 : :
1044 : : static void
1045 : 72223 : pgstat_free_entry(PgStatShared_HashEntry *shent, dshash_seq_status *hstat)
1046 : : {
1047 : : dsa_pointer pdsa;
1048 : 72223 : PgStat_Kind kind = shent->key.kind;
1049 : :
1050 : : /*
1051 : : * Fetch dsa pointer before deleting entry - that way we can free the
1052 : : * memory after releasing the lock.
1053 : : */
1054 : 72223 : pdsa = shent->body;
1055 : :
1056 [ + + ]: 72223 : if (!hstat)
1057 : 66499 : dshash_delete_entry(pgStatLocal.kind_hash[kind], shent);
1058 : : else
1059 : 5724 : dshash_delete_current(hstat);
1060 : :
1061 : 72223 : dsa_free(pgStatLocal.kind_dsa[kind], pdsa);
1062 : :
1063 : : /* Decrement entry count, if required. */
1064 [ + + ]: 72223 : if (pgstat_get_kind_info(kind)->track_entry_count)
1065 : 4 : pg_atomic_sub_fetch_u64(&pgStatLocal.shmem->entry_counts[kind - 1], 1);
1066 : 72223 : }
1067 : :
1068 : : /*
1069 : : * Helper for both pgstat_drop_database_and_contents() and
1070 : : * pgstat_drop_entry(). If hstat is non-null delete the shared entry using
1071 : : * dshash_delete_current(), otherwise use dshash_delete_entry(). In either
1072 : : * case the entry needs to be already locked.
1073 : : */
1074 : : static bool
1075 : 72253 : pgstat_drop_entry_internal(PgStatShared_HashEntry *shent,
1076 : : dshash_seq_status *hstat)
1077 : : {
1078 : : Assert(shent->body != InvalidDsaPointer);
1079 : :
1080 : : /* should already have released local reference */
1081 : 72253 : if (pgStatEntryRefHash)
1082 : : Assert(!pgstat_entry_ref_hash_lookup(pgStatEntryRefHash, shent->key));
1083 : :
1084 : : /*
1085 : : * Signal that the entry is dropped - this will eventually cause other
1086 : : * backends to release their references.
1087 : : */
1088 : : Assert(!shent->dropped);
1089 : 72253 : shent->dropped = true;
1090 : :
1091 : : /* release refcount marking entry as not dropped */
1092 [ + + ]: 72253 : if (pg_atomic_sub_fetch_u32(&shent->refcount, 1) == 0)
1093 : : {
1094 : 67088 : pgstat_free_entry(shent, hstat);
1095 : 67088 : return true;
1096 : : }
1097 : : else
1098 : : {
1099 [ + - ]: 5165 : if (!hstat)
1100 : 5165 : dshash_release_lock(pgStatLocal.kind_hash[shent->key.kind], shent);
1101 : 5165 : return false;
1102 : : }
1103 : : }
1104 : :
1105 : : /*
1106 : : * Drop stats for the database and all the objects inside that database.
1107 : : */
1108 : : static void
1109 : 47 : pgstat_drop_database_and_contents(Oid dboid)
1110 : : {
1111 : : dshash_seq_status hstat;
1112 : : PgStatShared_HashEntry *p;
1113 : 47 : uint64 not_freed_count = 0;
1114 : :
1115 : : Assert(OidIsValid(dboid));
1116 : :
1117 : : Assert(pgStatLocal.num_var_hashes > 0);
1118 : :
1119 : : /*
1120 : : * This backend might very well be the only backend holding a reference to
1121 : : * about-to-be-dropped entries. Ensure that we're not preventing it from
1122 : : * being cleaned up till later.
1123 : : *
1124 : : * Doing this separately from the dshash iteration below avoids having to
1125 : : * do so while holding a partition lock on any of the shared hashtables.
1126 : : */
1127 : 47 : pgstat_release_db_entry_refs(dboid);
1128 : :
1129 [ + + ]: 94 : for (int h = 0; h < pgStatLocal.num_var_hashes; h++)
1130 : : {
1131 : : /* some of the dshash entries are to be removed, take exclusive lock. */
1132 : 47 : dshash_seq_init(&hstat, pgStatLocal.var_hashes[h], true);
1133 [ + + ]: 18931 : while ((p = dshash_seq_next(&hstat)) != NULL)
1134 : : {
1135 [ + + ]: 18884 : if (p->dropped)
1136 : 1 : continue;
1137 : :
1138 [ + + ]: 18883 : if (p->key.dboid != dboid)
1139 : 13215 : continue;
1140 : :
1141 [ - + ]: 5668 : if (!pgstat_drop_entry_internal(p, &hstat))
1142 : : {
1143 : : /*
1144 : : * Even statistics for a dropped database might currently be
1145 : : * accessed (consider e.g. database stats for
1146 : : * pg_stat_database).
1147 : : */
1148 : 0 : not_freed_count++;
1149 : : }
1150 : : }
1151 : 47 : dshash_seq_term(&hstat);
1152 : : }
1153 : :
1154 : : /*
1155 : : * If some of the stats data could not be freed, signal the reference
1156 : : * holders to run garbage collection of their cached pgStatLocal.shmem.
1157 : : */
1158 [ - + ]: 47 : if (not_freed_count > 0)
1159 : 0 : pgstat_request_entry_refs_gc();
1160 : 47 : }
1161 : :
1162 : : /*
1163 : : * Drop a single stats entry.
1164 : : *
1165 : : * This routine returns false if the stats entry of the dropped object could
1166 : : * not be freed, true otherwise.
1167 : : *
1168 : : * If missing_ok is true, skip entries that have been concurrently dropped.
1169 : : *
1170 : : * The callers of this function should call pgstat_request_entry_refs_gc()
1171 : : * if the stats entry could not be freed, to ensure that this entry's memory
1172 : : * can be reclaimed later by a different backend calling
1173 : : * pgstat_gc_entry_refs().
1174 : : */
1175 : : bool
1176 : 93697 : pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid,
1177 : : bool missing_ok)
1178 : : {
1179 : 93697 : PgStat_HashKey key = {0};
1180 : : PgStatShared_HashEntry *shent;
1181 : 93697 : bool freed = true;
1182 : :
1183 : : Assert(kind >= PGSTAT_KIND_MIN && kind <= PGSTAT_KIND_MAX);
1184 : :
1185 : : /* Check case of unassigned kind ID */
1186 [ - + ]: 93697 : if (pgStatLocal.kind_hash[kind] == NULL)
1187 : : {
1188 [ # # ]: 0 : if (missing_ok)
1189 : 0 : return true;
1190 : :
1191 [ # # ]: 0 : elog(ERROR, "invalid stats kind for entry %u/%u/%" PRIu64,
1192 : : kind, dboid, objid);
1193 : : }
1194 : :
1195 : 93697 : key.kind = kind;
1196 : 93697 : key.dboid = dboid;
1197 : 93697 : key.objid = objid;
1198 : :
1199 : : /* delete local reference */
1200 [ + + ]: 93697 : if (pgStatEntryRefHash)
1201 : : {
1202 : : PgStat_EntryRefHashEntry *lohashent =
1203 : 89981 : pgstat_entry_ref_hash_lookup(pgStatEntryRefHash, key);
1204 : :
1205 [ + + ]: 89981 : if (lohashent)
1206 : 66361 : pgstat_release_entry_ref(lohashent->key, lohashent->entry_ref,
1207 : : true);
1208 : : }
1209 : :
1210 : : /* mark entry in the stats hashtable as deleted, drop if possible */
1211 : 93697 : shent = dshash_find(pgStatLocal.kind_hash[kind], &key, true);
1212 [ + + ]: 93697 : if (shent)
1213 : : {
1214 [ - + ]: 66529 : if (shent->dropped)
1215 : : {
1216 [ # # ]: 0 : if (!missing_ok)
1217 [ # # ]: 0 : elog(ERROR,
1218 : : "trying to drop stats entry already dropped: kind=%s dboid=%u objid=%" PRIu64 " refcount=%u generation=%u",
1219 : : pgstat_get_kind_info(shent->key.kind)->name,
1220 : : shent->key.dboid,
1221 : : shent->key.objid,
1222 : : pg_atomic_read_u32(&shent->refcount),
1223 : : pg_atomic_read_u32(&shent->generation));
1224 : 0 : dshash_release_lock(pgStatLocal.kind_hash[kind], shent);
1225 : 0 : return true;
1226 : : }
1227 : :
1228 : 66529 : freed = pgstat_drop_entry_internal(shent, NULL);
1229 : :
1230 : : /*
1231 : : * Database stats contain other stats. Drop those as well when
1232 : : * dropping the database. XXX: Perhaps this should be done in a
1233 : : * slightly more principled way? But not obvious what that'd look
1234 : : * like, and so far this is the only case...
1235 : : */
1236 [ + + ]: 66529 : if (key.kind == PGSTAT_KIND_DATABASE)
1237 : 47 : pgstat_drop_database_and_contents(key.dboid);
1238 : : }
1239 : :
1240 : 93697 : return freed;
1241 : : }
1242 : :
1243 : : /*
1244 : : * Scan through the stats hashtables, dropping statistics if approved by the
1245 : : * optional do_drop() function.
1246 : : */
1247 : : void
1248 : 269 : pgstat_drop_matching_entries(bool (*do_drop) (PgStatShared_HashEntry *, Datum),
1249 : : Datum match_data)
1250 : : {
1251 : : dshash_seq_status hstat;
1252 : : PgStatShared_HashEntry *ps;
1253 : 269 : uint64 not_freed_count = 0;
1254 : :
1255 [ + + ]: 539 : for (int h = 0; h < pgStatLocal.num_var_hashes; h++)
1256 : : {
1257 : : /* entries are removed, take an exclusive lock */
1258 : 270 : dshash_seq_init(&hstat, pgStatLocal.var_hashes[h], true);
1259 [ + + ]: 326 : while ((ps = dshash_seq_next(&hstat)) != NULL)
1260 : : {
1261 [ - + ]: 56 : if (ps->dropped)
1262 : 0 : continue;
1263 : :
1264 [ - + - - ]: 56 : if (do_drop != NULL && !do_drop(ps, match_data))
1265 : 0 : continue;
1266 : :
1267 : : /* delete local reference */
1268 [ - + ]: 56 : if (pgStatEntryRefHash)
1269 : : {
1270 : : PgStat_EntryRefHashEntry *lohashent =
1271 : 0 : pgstat_entry_ref_hash_lookup(pgStatEntryRefHash, ps->key);
1272 : :
1273 [ # # ]: 0 : if (lohashent)
1274 : 0 : pgstat_release_entry_ref(lohashent->key, lohashent->entry_ref,
1275 : : true);
1276 : : }
1277 : :
1278 [ - + ]: 56 : if (!pgstat_drop_entry_internal(ps, &hstat))
1279 : 0 : not_freed_count++;
1280 : : }
1281 : 270 : dshash_seq_term(&hstat);
1282 : : }
1283 : :
1284 [ - + ]: 269 : if (not_freed_count > 0)
1285 : 0 : pgstat_request_entry_refs_gc();
1286 : 269 : }
1287 : :
1288 : : /*
1289 : : * Scan through the stats hashtables and drop all entries.
1290 : : */
1291 : : void
1292 : 269 : pgstat_drop_all_entries(void)
1293 : : {
1294 : 269 : pgstat_drop_matching_entries(NULL, 0);
1295 : 269 : }
1296 : :
1297 : : static void
1298 : 11919 : shared_stat_reset_contents(PgStat_Kind kind, PgStatShared_Common *header,
1299 : : TimestampTz ts)
1300 : : {
1301 : 11919 : const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
1302 : :
1303 : 11919 : memset(pgstat_get_entry_data(kind, header), 0,
1304 : : pgstat_get_entry_len(kind));
1305 : :
1306 [ + + ]: 11919 : if (kind_info->reset_timestamp_cb)
1307 : 11915 : kind_info->reset_timestamp_cb(header, ts);
1308 : 11919 : }
1309 : :
1310 : : static void
1311 : 21 : pgstat_reset_matching_entries_in_hash(dshash_table *hash,
1312 : : bool (*do_reset) (PgStatShared_HashEntry *, Datum),
1313 : : Datum match_data,
1314 : : TimestampTz ts)
1315 : : {
1316 : : dshash_seq_status hstat;
1317 : : PgStatShared_HashEntry *p;
1318 : :
1319 : : /* dshash entry is not modified, take shared lock */
1320 : 21 : dshash_seq_init(&hstat, hash, false);
1321 [ + + ]: 16811 : while ((p = dshash_seq_next(&hstat)) != NULL)
1322 : : {
1323 : : PgStatShared_Common *header;
1324 : :
1325 [ + + ]: 16790 : if (p->dropped)
1326 : 1 : continue;
1327 : :
1328 [ + + ]: 16789 : if (!do_reset(p, match_data))
1329 : 5131 : continue;
1330 : :
1331 : 11658 : header = dsa_get_address(pgStatLocal.kind_dsa[p->key.kind], p->body);
1332 : :
1333 : 11658 : LWLockAcquire(&header->lock, LW_EXCLUSIVE);
1334 : :
1335 : 11658 : shared_stat_reset_contents(p->key.kind, header, ts);
1336 : :
1337 : 11658 : LWLockRelease(&header->lock);
1338 : : }
1339 : 21 : dshash_seq_term(&hstat);
1340 : 21 : }
1341 : :
1342 : : /*
1343 : : * Reset one variable-numbered stats entry.
1344 : : */
1345 : : void
1346 : 262 : pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
1347 : : {
1348 : : PgStat_EntryRef *entry_ref;
1349 : :
1350 : : Assert(!pgstat_get_kind_info(kind)->fixed_amount);
1351 : :
1352 : 262 : entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
1353 [ + + - + ]: 262 : if (!entry_ref || entry_ref->shared_entry->dropped)
1354 : 1 : return;
1355 : :
1356 : 261 : (void) pgstat_lock_entry(entry_ref, false);
1357 : 261 : shared_stat_reset_contents(kind, entry_ref->shared_stats, ts);
1358 : 261 : pgstat_unlock_entry(entry_ref);
1359 : : }
1360 : :
1361 : : /*
1362 : : * Scan through the stats hashtables, resetting statistics if approved by the
1363 : : * provided do_reset() function.
1364 : : */
1365 : : void
1366 : 15 : pgstat_reset_matching_entries(bool (*do_reset) (PgStatShared_HashEntry *, Datum),
1367 : : Datum match_data, TimestampTz ts)
1368 : : {
1369 [ + + ]: 30 : for (int h = 0; h < pgStatLocal.num_var_hashes; h++)
1370 : : {
1371 : 15 : pgstat_reset_matching_entries_in_hash(pgStatLocal.var_hashes[h],
1372 : : do_reset,
1373 : : match_data,
1374 : : ts);
1375 : : }
1376 : 15 : }
1377 : :
1378 : : static bool
1379 : 1548 : match_kind(PgStatShared_HashEntry *p, Datum match_data)
1380 : : {
1381 : 1548 : return p->key.kind == DatumGetInt32(match_data);
1382 : : }
1383 : :
1384 : : void
1385 : 6 : pgstat_reset_entries_of_kind(PgStat_Kind kind, TimestampTz ts)
1386 : : {
1387 : : Assert(pgStatLocal.kind_hash[kind] != NULL);
1388 : :
1389 : 6 : pgstat_reset_matching_entries_in_hash(pgStatLocal.kind_hash[kind],
1390 : : match_kind,
1391 : : Int32GetDatum(kind),
1392 : : ts);
1393 : 6 : }
1394 : :
1395 : : static void
1396 : 4551840 : pgstat_setup_memcxt(void)
1397 : : {
1398 [ + + ]: 4551840 : if (unlikely(!pgStatSharedRefContext))
1399 : 22331 : pgStatSharedRefContext =
1400 : 22331 : AllocSetContextCreate(TopMemoryContext,
1401 : : "PgStat Shared Ref",
1402 : : ALLOCSET_SMALL_SIZES);
1403 [ + + ]: 4551840 : if (unlikely(!pgStatEntryRefHashContext))
1404 : 22331 : pgStatEntryRefHashContext =
1405 : 22331 : AllocSetContextCreate(TopMemoryContext,
1406 : : "PgStat Shared Ref Hash",
1407 : : ALLOCSET_SMALL_SIZES);
1408 : 4551840 : }
|