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 : 5324 : 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 : 5324 : sz = 256 * 1024;
127 : : Assert(dsa_minimum_size() <= sz);
128 : 5324 : return MAXALIGN(sz);
129 : : }
130 : :
131 : : /*
132 : : * Compute shared memory space needed for cumulative statistics
133 : : */
134 : : static Size
135 : 1331 : StatsShmemSize(void)
136 : : {
137 : : Size sz;
138 : :
139 : 1331 : sz = MAXALIGN(sizeof(PgStat_ShmemControl));
140 : 1331 : sz = add_size(sz, pgstat_dsa_init_size());
141 : :
142 : : /* Add per-kind DSA space for variable-numbered own_hash kinds. */
143 [ + + ]: 43923 : for (PgStat_Kind kind = PGSTAT_KIND_MIN; kind <= PGSTAT_KIND_MAX; kind++)
144 : : {
145 : 42592 : const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
146 : :
147 [ + + + + : 42592 : if (!kind_info || kind_info->fixed_amount || !kind_info->own_hash)
+ + ]
148 : 42589 : 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 [ + + ]: 13310 : for (PgStat_Kind kind = PGSTAT_KIND_CUSTOM_MIN; kind <= PGSTAT_KIND_CUSTOM_MAX; kind++)
155 : : {
156 : 11979 : const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
157 : :
158 [ + + ]: 11979 : if (!kind_info)
159 : 11961 : 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 : 1331 : return sz;
168 : : }
169 : :
170 : : /*
171 : : * Register shared memory area for cumulative statistics
172 : : */
173 : : static void
174 : 1331 : StatsShmemRequest(void *arg)
175 : : {
176 : 1331 : ShmemRequestStruct(.name = "Shared Memory Stats",
177 : : .size = StatsShmemSize(),
178 : : .ptr = (void **) &pgStatLocal.shmem,
179 : : );
180 : 1331 : }
181 : :
182 : : /*
183 : : * Initialize cumulative statistics system during startup
184 : : */
185 : : static void
186 : 1327 : StatsShmemInit(void *arg)
187 : : {
188 : : dsa_area *dsa;
189 : : dshash_table *dsh;
190 : 1327 : PgStat_ShmemControl *ctl = pgStatLocal.shmem;
191 : 1327 : char *p = (char *) ctl;
192 : :
193 : : /* the allocation of pgStatLocal.shmem itself */
194 : 1327 : 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 : 1327 : ctl->raw_dsa_area = p;
202 : 1327 : p += pgstat_dsa_init_size();
203 : 1327 : dsa = dsa_create_in_place(ctl->raw_dsa_area,
204 : : pgstat_dsa_init_size(),
205 : : LWTRANCHE_PGSTATS_DSA, NULL);
206 : 1327 : 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 : 1327 : 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 : 1327 : dsh = dshash_create(dsa, &dsh_params, NULL);
219 : 1327 : ctl->hash_handle = dshash_get_hash_table_handle(dsh);
220 : :
221 : : /* lift limit set above */
222 : 1327 : 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 : 1327 : memset(ctl->raw_kind_dsa_area, 0, sizeof(ctl->raw_kind_dsa_area));
229 [ + + ]: 43791 : for (PgStat_Kind kind = PGSTAT_KIND_MIN; kind <= PGSTAT_KIND_MAX; kind++)
230 : : {
231 : 42464 : const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
232 : : dsa_area *kind_dsa;
233 : : dshash_table *kind_dsh;
234 : :
235 [ + + + + : 42464 : if (!kind_info || kind_info->fixed_amount || !kind_info->own_hash)
+ + ]
236 : 42461 : 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 : 1327 : dshash_detach(dsh);
264 : 1327 : dsa_detach(dsa);
265 : :
266 : 1327 : pg_atomic_init_u64(&ctl->gc_request_count, 1);
267 : :
268 : : /* Do the per-kind initialization */
269 [ + + ]: 43791 : for (PgStat_Kind kind = PGSTAT_KIND_MIN; kind <= PGSTAT_KIND_MAX; kind++)
270 : : {
271 : 42464 : const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
272 : : char *ptr;
273 : :
274 [ + + ]: 42464 : if (!kind_info)
275 : 23868 : continue;
276 : :
277 : : /* initialize entry count tracking */
278 [ + + ]: 18596 : 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 [ + + ]: 18596 : if (kind_info->fixed_amount)
283 : : {
284 [ + + ]: 9298 : if (pgstat_is_kind_builtin(kind))
285 : 9289 : 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 : 9298 : kind_info->init_shmem_cb(ptr);
297 : : }
298 : : }
299 : 1327 : }
300 : :
301 : : void
302 : 26217 : 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 : 26217 : oldcontext = MemoryContextSwitchTo(TopMemoryContext);
312 : :
313 : 26217 : shared_dsa = dsa_attach_in_place(pgStatLocal.shmem->raw_dsa_area,
314 : : NULL);
315 : 26217 : dsa_pin_mapping(shared_dsa);
316 : :
317 : 26217 : shared_hash = dshash_attach(shared_dsa, &dsh_params,
318 : 26217 : pgStatLocal.shmem->hash_handle,
319 : : NULL);
320 : :
321 : : /* Build per-kind mappings for the hash tables. */
322 : 26217 : pgStatLocal.var_hashes[pgStatLocal.num_var_hashes++] = shared_hash;
323 : :
324 [ + + ]: 865161 : for (PgStat_Kind kind = PGSTAT_KIND_MIN; kind <= PGSTAT_KIND_MAX; kind++)
325 : : {
326 : 838944 : const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
327 : :
328 [ + + ]: 838944 : 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 [ + + + + ]: 838906 : else if (kind_info && !kind_info->fixed_amount)
347 : : {
348 : : /* main shared hashtable */
349 : 183581 : pgStatLocal.kind_hash[kind] = shared_hash;
350 : 183581 : pgStatLocal.kind_dsa[kind] = shared_dsa;
351 : : }
352 : : else
353 : : {
354 : : /* unassigned kind ID */
355 : 655325 : pgStatLocal.kind_hash[kind] = NULL;
356 : 655325 : pgStatLocal.kind_dsa[kind] = NULL;
357 : : }
358 : : }
359 : :
360 : 26217 : MemoryContextSwitchTo(oldcontext);
361 : 26217 : }
362 : :
363 : : void
364 : 26217 : pgstat_detach_shmem(void)
365 : : {
366 : : Assert(pgStatLocal.num_var_hashes > 0);
367 : :
368 : : /* we shouldn't leave references to shared stats */
369 : 26217 : pgstat_release_all_entry_refs(false);
370 : :
371 [ + + ]: 52472 : for (int h = 0; h < pgStatLocal.num_var_hashes; h++)
372 : : {
373 : 26255 : dsa_area *dsa = dshash_get_dsa_area(pgStatLocal.var_hashes[h]);
374 : :
375 : 26255 : dshash_detach(pgStatLocal.var_hashes[h]);
376 : 26255 : dsa_detach(dsa);
377 : 26255 : pgStatLocal.var_hashes[h] = NULL;
378 : : }
379 : :
380 : 26217 : pgStatLocal.num_var_hashes = 0;
381 : :
382 : : /* Reset per-kind mappings. */
383 [ + + ]: 865161 : for (PgStat_Kind kind = PGSTAT_KIND_MIN; kind <= PGSTAT_KIND_MAX; kind++)
384 : : {
385 : 838944 : pgStatLocal.kind_hash[kind] = NULL;
386 : 838944 : 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 : 26217 : dsa_release_in_place(pgStatLocal.shmem->raw_dsa_area);
395 : :
396 [ + + ]: 865161 : for (PgStat_Kind kind = PGSTAT_KIND_MIN; kind <= PGSTAT_KIND_MAX; kind++)
397 : : {
398 [ + + ]: 838944 : if (pgStatLocal.shmem->raw_kind_dsa_area[kind] != NULL)
399 : 38 : dsa_release_in_place(pgStatLocal.shmem->raw_kind_dsa_area[kind]);
400 : : }
401 : 26217 : }
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 : 394321 : pgstat_alloc_entry_body(PgStat_Kind kind)
416 : : {
417 : 394321 : const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
418 : :
419 : 788642 : return dsa_allocate_extended(pgStatLocal.kind_dsa[kind],
420 : 394321 : 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 : 394320 : pgstat_init_entry(PgStat_Kind kind,
432 : : PgStatShared_HashEntry *shhashent,
433 : : dsa_pointer chunk)
434 : : {
435 : : PgStatShared_Common *shheader;
436 : 394320 : 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 : 394320 : pg_atomic_init_u32(&shhashent->refcount, 1);
447 : :
448 : : /*
449 : : * Initialize "generation" to 0, as freshly created.
450 : : */
451 : 394320 : pg_atomic_init_u32(&shhashent->generation, 0);
452 : 394320 : shhashent->dropped = false;
453 : :
454 : 394320 : shheader = dsa_get_address(pgStatLocal.kind_dsa[kind], chunk);
455 : 394320 : shheader->magic = 0xdeadbeef;
456 : :
457 : : /* Link the new entry from the hash entry. */
458 : 394320 : shhashent->body = chunk;
459 : :
460 : : /* Increment entry count, if required. */
461 [ + + ]: 394320 : if (kind_info->track_entry_count)
462 : 12 : pg_atomic_fetch_add_u64(&pgStatLocal.shmem->entry_counts[kind - 1], 1);
463 : :
464 : 394320 : LWLockInitialize(&shheader->lock, LWTRANCHE_PGSTATS_DATA);
465 : :
466 : 394320 : return shheader;
467 : : }
468 : :
469 : : static PgStatShared_Common *
470 : 32 : pgstat_reinit_entry(PgStat_Kind kind, PgStatShared_HashEntry *shhashent)
471 : : {
472 : : PgStatShared_Common *shheader;
473 : :
474 : 32 : shheader = dsa_get_address(pgStatLocal.kind_dsa[kind],
475 : : shhashent->body);
476 : :
477 : : /* mark as not dropped anymore */
478 : 32 : 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 : 32 : pg_atomic_fetch_add_u32(&shhashent->generation, 1);
485 : 32 : shhashent->dropped = false;
486 : :
487 : : /* reinitialize content */
488 : : Assert(shheader->magic == 0xdeadbeef);
489 : 32 : memset(pgstat_get_entry_data(kind, shheader), 0,
490 : : pgstat_get_entry_len(kind));
491 : :
492 : 32 : return shheader;
493 : : }
494 : :
495 : : static void
496 : 4550005 : pgstat_setup_shared_refs(void)
497 : : {
498 [ + + ]: 4550005 : if (likely(pgStatEntryRefHash != NULL))
499 : 4527512 : return;
500 : :
501 : 22493 : pgStatEntryRefHash =
502 : 22493 : pgstat_entry_ref_hash_create(pgStatEntryRefHashContext,
503 : : PGSTAT_ENTRY_REF_HASH_SIZE, NULL);
504 : 22493 : 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 : 1193254 : 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 : 1193254 : pg_atomic_fetch_add_u32(&shhashent->refcount, 1);
521 : :
522 : 1193254 : entry_ref->shared_stats = shheader;
523 : 1193254 : entry_ref->shared_entry = shhashent;
524 : 1193254 : 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 : 1193254 : dshash_release_lock(hash, shhashent);
532 : 1193254 : }
533 : :
534 : : /*
535 : : * Helper function for pgstat_get_entry_ref().
536 : : */
537 : : static bool
538 : 4550005 : 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 : 4550005 : cache_entry = pgstat_entry_ref_hash_insert(pgStatEntryRefHash, key, &found);
550 : :
551 [ + + - + ]: 4550005 : if (!found || !cache_entry->entry_ref)
552 : 1299666 : {
553 : : PgStat_EntryRef *entry_ref;
554 : :
555 : 1299666 : entry_ref = MemoryContextAllocExtended(pgStatSharedRefContext,
556 : : sizeof(PgStat_EntryRef),
557 : : MCXT_ALLOC_NO_OOM);
558 [ - + ]: 1299666 : 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 : 1299666 : cache_entry->entry_ref = entry_ref;
572 : 1299666 : entry_ref->shared_stats = NULL;
573 : 1299666 : entry_ref->shared_entry = NULL;
574 : 1299666 : entry_ref->pending = NULL;
575 : :
576 : 1299666 : found = false;
577 : : }
578 [ - + ]: 3250339 : 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 : 3250339 : 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 : 4550005 : *entry_ref_p = cache_entry->entry_ref;
597 : 4550005 : 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 : 4550005 : pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, uint64 objid, bool create,
609 : : bool *created_entry)
610 : : {
611 : 4550005 : PgStat_HashKey key = {0};
612 : : PgStatShared_HashEntry *shhashent;
613 : 4550005 : PgStatShared_Common *shheader = NULL;
614 : : PgStat_EntryRef *entry_ref;
615 : : dshash_table *hash;
616 : : dsa_area *dsa;
617 : :
618 : 4550005 : key.kind = kind;
619 : 4550005 : key.dboid = dboid;
620 : 4550005 : 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 : 4550005 : pgstat_setup_memcxt();
632 : 4550005 : pgstat_setup_shared_refs();
633 : :
634 [ + + ]: 4550005 : 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 [ + + ]: 4550005 : if (pgstat_need_entry_refs_gc())
645 : 6450 : 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 [ + + ]: 4550005 : if (pgstat_get_entry_ref_cached(key, &entry_ref))
652 : 3250339 : return entry_ref;
653 : :
654 : : Assert(entry_ref != NULL);
655 : :
656 : : /* Per-kind hashtable and DSA area */
657 : 1299666 : hash = pgStatLocal.kind_hash[kind];
658 : 1299666 : 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 : 1299666 : shhashent = dshash_find(hash, &key, false);
665 : :
666 [ + + + + ]: 1299666 : if (create && !shhashent)
667 : : {
668 : : bool shfound;
669 : : dsa_pointer chunk;
670 : :
671 : : /* Allocate the stats body before inserting a hash entry. */
672 : 148554 : chunk = pgstat_alloc_entry_body(kind);
673 [ - + ]: 148554 : 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 : 148554 : shhashent = dshash_find_or_insert_extended(hash,
689 : : &key, &shfound,
690 : : DSHASH_INSERT_NO_OOM);
691 [ - + ]: 148554 : 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 [ + + ]: 148554 : if (!shfound)
708 : : {
709 : 148553 : shheader = pgstat_init_entry(kind, shhashent, chunk);
710 : 148553 : pgstat_acquire_entry_ref(entry_ref, hash, shhashent, shheader);
711 : :
712 [ + + ]: 148553 : if (created_entry != NULL)
713 : 51 : *created_entry = true;
714 : :
715 : 148553 : return entry_ref;
716 : : }
717 : :
718 : : /* Concurrent insert won; drop the unused body. */
719 : 1 : dsa_free(dsa, chunk);
720 : : }
721 : :
722 [ + + ]: 1151113 : 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 : 106375 : pgstat_release_entry_ref(key, entry_ref, false);
730 : :
731 : 106375 : 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 [ + + + + ]: 1044738 : 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 : 32 : shheader = pgstat_reinit_entry(kind, shhashent);
752 : 32 : pgstat_acquire_entry_ref(entry_ref, hash, shhashent, shheader);
753 : :
754 [ - + ]: 32 : if (created_entry != NULL)
755 : 0 : *created_entry = true;
756 : :
757 : 32 : return entry_ref;
758 : : }
759 [ + + ]: 1044706 : 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 : 1044669 : shheader = dsa_get_address(dsa, shhashent->body);
769 : 1044669 : pgstat_acquire_entry_ref(entry_ref, hash, shhashent, shheader);
770 : :
771 : 1044669 : return entry_ref;
772 : : }
773 : : }
774 : : }
775 : :
776 : : static void
777 : 1299666 : pgstat_release_entry_ref(PgStat_HashKey key, PgStat_EntryRef *entry_ref,
778 : : bool discard_pending)
779 : : {
780 [ + - + + ]: 1299666 : if (entry_ref && entry_ref->pending)
781 : : {
782 [ + - ]: 44320 : if (discard_pending)
783 : 44320 : pgstat_delete_pending_entry(entry_ref);
784 : : else
785 [ # # ]: 0 : elog(ERROR, "releasing ref with pending data");
786 : : }
787 : :
788 [ + - + + ]: 1299666 : 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 [ + + ]: 1193254 : 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. Before we get the lock, the entry may
804 : : * have been reinitialized, or freed and created again, so look it
805 : : * up by key. If it is gone, it got already dropped and freed.
806 : : */
807 : 5133 : shent = dshash_find(pgStatLocal.kind_hash[key.kind], &key, true);
808 : :
809 [ + - ]: 5133 : if (shent)
810 : : {
811 [ + - + - ]: 10266 : if (shent->dropped &&
812 : 5133 : pg_atomic_read_u32(&shent->refcount) == 0)
813 : : {
814 : : /* Entry dropped and unreferenced, so free it. */
815 : 5133 : pgstat_free_entry(shent, NULL);
816 : : }
817 : : else
818 : : {
819 : : /* Entry reinitialized or created again, still in use. */
820 : 0 : dshash_release_lock(pgStatLocal.kind_hash[key.kind], shent);
821 : : }
822 : : }
823 : : }
824 : : }
825 : :
826 [ - + ]: 1299666 : if (!pgstat_entry_ref_hash_delete(pgStatEntryRefHash, key))
827 [ # # ]: 0 : elog(ERROR, "entry ref vanished before deletion");
828 : :
829 [ + - ]: 1299666 : if (entry_ref)
830 : 1299666 : pfree(entry_ref);
831 : 1299666 : }
832 : :
833 : : /*
834 : : * Acquire exclusive lock on the entry.
835 : : *
836 : : * If nowait is true, it's just a conditional acquire, and the result
837 : : * *must* be checked to verify success.
838 : : * If nowait is false, waits as necessary, always returning true.
839 : : */
840 : : bool
841 : 1543700 : pgstat_lock_entry(PgStat_EntryRef *entry_ref, bool nowait)
842 : : {
843 : 1543700 : LWLock *lock = &entry_ref->shared_stats->lock;
844 : :
845 [ + + ]: 1543700 : if (nowait)
846 : 426985 : return LWLockConditionalAcquire(lock, LW_EXCLUSIVE);
847 : :
848 : 1116715 : LWLockAcquire(lock, LW_EXCLUSIVE);
849 : 1116715 : return true;
850 : : }
851 : :
852 : : /*
853 : : * Acquire shared lock on the entry.
854 : : *
855 : : * Separate from pgstat_lock_entry() as most callers will need to lock
856 : : * exclusively. The wait semantics are identical.
857 : : */
858 : : bool
859 : 292763 : pgstat_lock_entry_shared(PgStat_EntryRef *entry_ref, bool nowait)
860 : : {
861 : 292763 : LWLock *lock = &entry_ref->shared_stats->lock;
862 : :
863 [ - + ]: 292763 : if (nowait)
864 : 0 : return LWLockConditionalAcquire(lock, LW_SHARED);
865 : :
866 : 292763 : LWLockAcquire(lock, LW_SHARED);
867 : 292763 : return true;
868 : : }
869 : :
870 : : void
871 : 1836440 : pgstat_unlock_entry(PgStat_EntryRef *entry_ref)
872 : : {
873 : 1836440 : LWLockRelease(&entry_ref->shared_stats->lock);
874 : 1836440 : }
875 : :
876 : : /*
877 : : * Helper function to fetch and lock shared stats.
878 : : */
879 : : PgStat_EntryRef *
880 : 326294 : pgstat_get_entry_ref_locked(PgStat_Kind kind, Oid dboid, uint64 objid,
881 : : bool nowait)
882 : : {
883 : : PgStat_EntryRef *entry_ref;
884 : :
885 : : /* find shared table stats entry corresponding to the local entry */
886 : 326294 : entry_ref = pgstat_get_entry_ref(kind, dboid, objid, true, NULL);
887 : :
888 : : /* lock the shared entry to protect the content, skip if failed */
889 [ - + ]: 326294 : if (!pgstat_lock_entry(entry_ref, nowait))
890 : 0 : return NULL;
891 : :
892 : 326294 : return entry_ref;
893 : : }
894 : :
895 : : void
896 : 2049 : pgstat_request_entry_refs_gc(void)
897 : : {
898 : 2049 : pg_atomic_fetch_add_u64(&pgStatLocal.shmem->gc_request_count, 1);
899 : 2049 : }
900 : :
901 : : static bool
902 : 4550005 : pgstat_need_entry_refs_gc(void)
903 : : {
904 : : uint64 curage;
905 : :
906 [ - + ]: 4550005 : if (!pgStatEntryRefHash)
907 : 0 : return false;
908 : :
909 : : /* should have been initialized when creating pgStatEntryRefHash */
910 : : Assert(pgStatSharedRefAge != 0);
911 : :
912 : 4550005 : curage = pg_atomic_read_u64(&pgStatLocal.shmem->gc_request_count);
913 : :
914 : 4550005 : return pgStatSharedRefAge != curage;
915 : : }
916 : :
917 : : static void
918 : 6450 : pgstat_gc_entry_refs(void)
919 : : {
920 : : pgstat_entry_ref_hash_iterator i;
921 : : PgStat_EntryRefHashEntry *ent;
922 : : uint64 curage;
923 : :
924 : 6450 : curage = pg_atomic_read_u64(&pgStatLocal.shmem->gc_request_count);
925 : : Assert(curage != 0);
926 : :
927 : : /*
928 : : * Some entries have been dropped or reinitialized. Invalidate cache
929 : : * pointer to them.
930 : : */
931 : 6450 : pgstat_entry_ref_hash_start_iterate(pgStatEntryRefHash, &i);
932 [ + + ]: 485036 : while ((ent = pgstat_entry_ref_hash_iterate(pgStatEntryRefHash, &i)) != NULL)
933 : : {
934 : 478586 : PgStat_EntryRef *entry_ref = ent->entry_ref;
935 : :
936 : : Assert(!entry_ref->shared_stats ||
937 : : entry_ref->shared_stats->magic == 0xdeadbeef);
938 : :
939 : : /* A NULL shared_entry marks a partial reference. */
940 [ - + ]: 478586 : if (entry_ref->shared_entry == NULL)
941 : : {
942 : : Assert(entry_ref->shared_stats == NULL);
943 : : Assert(entry_ref->pending == NULL);
944 : 0 : pgstat_release_entry_ref(ent->key, entry_ref, false);
945 : 0 : continue;
946 : : }
947 : :
948 : : /*
949 : : * "generation" checks for the case of entries being reinitialized,
950 : : * and "dropped" for the case where these are.. dropped.
951 : : */
952 [ + + ]: 478586 : if (!entry_ref->shared_entry->dropped &&
953 : 348335 : pg_atomic_read_u32(&entry_ref->shared_entry->generation) ==
954 [ + + ]: 348335 : entry_ref->generation)
955 : 348304 : continue;
956 : :
957 : : /* cannot gc shared ref that has pending data */
958 [ + + ]: 130282 : if (entry_ref->pending != NULL)
959 : 125585 : continue;
960 : :
961 : 4697 : pgstat_release_entry_ref(ent->key, entry_ref, false);
962 : : }
963 : :
964 : 6450 : pgStatSharedRefAge = curage;
965 : 6450 : }
966 : :
967 : : static void
968 : 22541 : pgstat_release_matching_entry_refs(bool discard_pending, ReleaseMatchCB match,
969 : : Datum match_data)
970 : : {
971 : : pgstat_entry_ref_hash_iterator i;
972 : : PgStat_EntryRefHashEntry *ent;
973 : :
974 [ + + ]: 22541 : if (pgStatEntryRefHash == NULL)
975 : 1 : return;
976 : :
977 : 22540 : pgstat_entry_ref_hash_start_iterate(pgStatEntryRefHash, &i);
978 : :
979 : 1145793 : while ((ent = pgstat_entry_ref_hash_iterate(pgStatEntryRefHash, &i))
980 [ + + ]: 1145793 : != NULL)
981 : : {
982 : : Assert(ent->entry_ref != NULL);
983 : :
984 [ + + + - ]: 1123253 : if (match && !match(ent, match_data))
985 : 1433 : continue;
986 : :
987 : 1121820 : pgstat_release_entry_ref(ent->key, ent->entry_ref, discard_pending);
988 : : }
989 : : }
990 : :
991 : : /*
992 : : * Release all local references to shared stats entries.
993 : : *
994 : : * When a process exits it cannot do so while still holding references onto
995 : : * stats entries, otherwise the shared stats entries could never be freed.
996 : : */
997 : : static void
998 : 26217 : pgstat_release_all_entry_refs(bool discard_pending)
999 : : {
1000 [ + + ]: 26217 : if (pgStatEntryRefHash == NULL)
1001 : 3724 : return;
1002 : :
1003 : 22493 : pgstat_release_matching_entry_refs(discard_pending, NULL, 0);
1004 : : Assert(pgStatEntryRefHash->members == 0);
1005 : 22493 : pgstat_entry_ref_hash_destroy(pgStatEntryRefHash);
1006 : 22493 : pgStatEntryRefHash = NULL;
1007 : : }
1008 : :
1009 : : static bool
1010 : 1433 : match_db(PgStat_EntryRefHashEntry *ent, Datum match_data)
1011 : : {
1012 : 1433 : Oid dboid = DatumGetObjectId(match_data);
1013 : :
1014 : 1433 : return ent->key.dboid == dboid;
1015 : : }
1016 : :
1017 : : static void
1018 : 48 : pgstat_release_db_entry_refs(Oid dboid)
1019 : : {
1020 : 48 : pgstat_release_matching_entry_refs( /* discard pending = */ true,
1021 : : match_db,
1022 : : ObjectIdGetDatum(dboid));
1023 : 48 : }
1024 : :
1025 : :
1026 : : /* ------------------------------------------------------------
1027 : : * Dropping and resetting of stats entries
1028 : : * ------------------------------------------------------------
1029 : : */
1030 : :
1031 : : static void
1032 : 72646 : pgstat_free_entry(PgStatShared_HashEntry *shent, dshash_seq_status *hstat)
1033 : : {
1034 : : dsa_pointer pdsa;
1035 : 72646 : PgStat_Kind kind = shent->key.kind;
1036 : :
1037 : : /*
1038 : : * Fetch dsa pointer before deleting entry - that way we can free the
1039 : : * memory after releasing the lock.
1040 : : */
1041 : 72646 : pdsa = shent->body;
1042 : :
1043 [ + + ]: 72646 : if (!hstat)
1044 : 66875 : dshash_delete_entry(pgStatLocal.kind_hash[kind], shent);
1045 : : else
1046 : 5771 : dshash_delete_current(hstat);
1047 : :
1048 : 72646 : dsa_free(pgStatLocal.kind_dsa[kind], pdsa);
1049 : :
1050 : : /* Decrement entry count, if required. */
1051 [ + + ]: 72646 : if (pgstat_get_kind_info(kind)->track_entry_count)
1052 : 4 : pg_atomic_sub_fetch_u64(&pgStatLocal.shmem->entry_counts[kind - 1], 1);
1053 : 72646 : }
1054 : :
1055 : : /*
1056 : : * Helper for both pgstat_drop_database_and_contents() and
1057 : : * pgstat_drop_entry(). If hstat is non-null delete the shared entry using
1058 : : * dshash_delete_current(), otherwise use dshash_delete_entry(). In either
1059 : : * case the entry needs to be already locked.
1060 : : */
1061 : : static bool
1062 : 72678 : pgstat_drop_entry_internal(PgStatShared_HashEntry *shent,
1063 : : dshash_seq_status *hstat)
1064 : : {
1065 : : Assert(shent->body != InvalidDsaPointer);
1066 : :
1067 : : /* should already have released local reference */
1068 : 72678 : if (pgStatEntryRefHash)
1069 : : Assert(!pgstat_entry_ref_hash_lookup(pgStatEntryRefHash, shent->key));
1070 : :
1071 : : /*
1072 : : * Signal that the entry is dropped - this will eventually cause other
1073 : : * backends to release their references.
1074 : : */
1075 : : Assert(!shent->dropped);
1076 : 72678 : shent->dropped = true;
1077 : :
1078 : : /* release refcount marking entry as not dropped */
1079 [ + + ]: 72678 : if (pg_atomic_sub_fetch_u32(&shent->refcount, 1) == 0)
1080 : : {
1081 : 67513 : pgstat_free_entry(shent, hstat);
1082 : 67513 : return true;
1083 : : }
1084 : : else
1085 : : {
1086 [ + - ]: 5165 : if (!hstat)
1087 : 5165 : dshash_release_lock(pgStatLocal.kind_hash[shent->key.kind], shent);
1088 : 5165 : return false;
1089 : : }
1090 : : }
1091 : :
1092 : : /*
1093 : : * Drop stats for the database and all the objects inside that database.
1094 : : */
1095 : : static void
1096 : 48 : pgstat_drop_database_and_contents(Oid dboid)
1097 : : {
1098 : : dshash_seq_status hstat;
1099 : : PgStatShared_HashEntry *p;
1100 : 48 : uint64 not_freed_count = 0;
1101 : :
1102 : : Assert(OidIsValid(dboid));
1103 : :
1104 : : Assert(pgStatLocal.num_var_hashes > 0);
1105 : :
1106 : : /*
1107 : : * This backend might very well be the only backend holding a reference to
1108 : : * about-to-be-dropped entries. Ensure that we're not preventing it from
1109 : : * being cleaned up till later.
1110 : : *
1111 : : * Doing this separately from the dshash iteration below avoids having to
1112 : : * do so while holding a partition lock on any of the shared hashtables.
1113 : : */
1114 : 48 : pgstat_release_db_entry_refs(dboid);
1115 : :
1116 [ + + ]: 96 : for (int h = 0; h < pgStatLocal.num_var_hashes; h++)
1117 : : {
1118 : : /* some of the dshash entries are to be removed, take exclusive lock. */
1119 : 48 : dshash_seq_init(&hstat, pgStatLocal.var_hashes[h], true);
1120 [ + + ]: 19341 : while ((p = dshash_seq_next(&hstat)) != NULL)
1121 : : {
1122 [ + + ]: 19293 : if (p->dropped)
1123 : 2 : continue;
1124 : :
1125 [ + + ]: 19291 : if (p->key.dboid != dboid)
1126 : 13576 : continue;
1127 : :
1128 [ - + ]: 5715 : if (!pgstat_drop_entry_internal(p, &hstat))
1129 : : {
1130 : : /*
1131 : : * Even statistics for a dropped database might currently be
1132 : : * accessed (consider e.g. database stats for
1133 : : * pg_stat_database).
1134 : : */
1135 : 0 : not_freed_count++;
1136 : : }
1137 : : }
1138 : 48 : dshash_seq_term(&hstat);
1139 : : }
1140 : :
1141 : : /*
1142 : : * If some of the stats data could not be freed, signal the reference
1143 : : * holders to run garbage collection of their cached pgStatLocal.shmem.
1144 : : */
1145 [ - + ]: 48 : if (not_freed_count > 0)
1146 : 0 : pgstat_request_entry_refs_gc();
1147 : 48 : }
1148 : :
1149 : : /*
1150 : : * Drop a single stats entry.
1151 : : *
1152 : : * This routine returns false if the stats entry of the dropped object could
1153 : : * not be freed, true otherwise.
1154 : : *
1155 : : * If missing_ok is true, skip entries that have been concurrently dropped.
1156 : : *
1157 : : * The callers of this function should call pgstat_request_entry_refs_gc()
1158 : : * if the stats entry could not be freed, to ensure that this entry's memory
1159 : : * can be reclaimed later by a different backend calling
1160 : : * pgstat_gc_entry_refs().
1161 : : */
1162 : : bool
1163 : 94230 : pgstat_drop_entry(PgStat_Kind kind, Oid dboid, uint64 objid,
1164 : : bool missing_ok)
1165 : : {
1166 : 94230 : PgStat_HashKey key = {0};
1167 : : PgStatShared_HashEntry *shent;
1168 : 94230 : bool freed = true;
1169 : :
1170 : : Assert(kind >= PGSTAT_KIND_MIN && kind <= PGSTAT_KIND_MAX);
1171 : :
1172 : : /* Check case of unassigned kind ID */
1173 [ - + ]: 94230 : if (pgStatLocal.kind_hash[kind] == NULL)
1174 : : {
1175 [ # # ]: 0 : if (missing_ok)
1176 : 0 : return true;
1177 : :
1178 [ # # ]: 0 : elog(ERROR, "invalid stats kind for entry %u/%u/%" PRIu64,
1179 : : kind, dboid, objid);
1180 : : }
1181 : :
1182 : 94230 : key.kind = kind;
1183 : 94230 : key.dboid = dboid;
1184 : 94230 : key.objid = objid;
1185 : :
1186 : : /* delete local reference */
1187 [ + + ]: 94230 : if (pgStatEntryRefHash)
1188 : : {
1189 : : PgStat_EntryRefHashEntry *lohashent =
1190 : 90499 : pgstat_entry_ref_hash_lookup(pgStatEntryRefHash, key);
1191 : :
1192 [ + + ]: 90499 : if (lohashent)
1193 : 66737 : pgstat_release_entry_ref(lohashent->key, lohashent->entry_ref,
1194 : : true);
1195 : : }
1196 : :
1197 : : /* mark entry in the stats hashtable as deleted, drop if possible */
1198 : 94230 : shent = dshash_find(pgStatLocal.kind_hash[kind], &key, true);
1199 [ + + ]: 94230 : if (shent)
1200 : : {
1201 [ - + ]: 66907 : if (shent->dropped)
1202 : : {
1203 [ # # ]: 0 : if (!missing_ok)
1204 [ # # ]: 0 : elog(ERROR,
1205 : : "trying to drop stats entry already dropped: kind=%s dboid=%u objid=%" PRIu64 " refcount=%u generation=%u",
1206 : : pgstat_get_kind_info(shent->key.kind)->name,
1207 : : shent->key.dboid,
1208 : : shent->key.objid,
1209 : : pg_atomic_read_u32(&shent->refcount),
1210 : : pg_atomic_read_u32(&shent->generation));
1211 : 0 : dshash_release_lock(pgStatLocal.kind_hash[kind], shent);
1212 : 0 : return true;
1213 : : }
1214 : :
1215 : 66907 : freed = pgstat_drop_entry_internal(shent, NULL);
1216 : :
1217 : : /*
1218 : : * Database stats contain other stats. Drop those as well when
1219 : : * dropping the database. XXX: Perhaps this should be done in a
1220 : : * slightly more principled way? But not obvious what that'd look
1221 : : * like, and so far this is the only case...
1222 : : */
1223 [ + + ]: 66907 : if (key.kind == PGSTAT_KIND_DATABASE)
1224 : 48 : pgstat_drop_database_and_contents(key.dboid);
1225 : : }
1226 : :
1227 : 94230 : return freed;
1228 : : }
1229 : :
1230 : : /*
1231 : : * Scan through the stats hashtables, dropping statistics if approved by the
1232 : : * optional do_drop() function.
1233 : : */
1234 : : void
1235 : 270 : pgstat_drop_matching_entries(bool (*do_drop) (PgStatShared_HashEntry *, Datum),
1236 : : Datum match_data)
1237 : : {
1238 : : dshash_seq_status hstat;
1239 : : PgStatShared_HashEntry *ps;
1240 : 270 : uint64 not_freed_count = 0;
1241 : :
1242 [ + + ]: 541 : for (int h = 0; h < pgStatLocal.num_var_hashes; h++)
1243 : : {
1244 : : /* entries are removed, take an exclusive lock */
1245 : 271 : dshash_seq_init(&hstat, pgStatLocal.var_hashes[h], true);
1246 [ + + ]: 327 : while ((ps = dshash_seq_next(&hstat)) != NULL)
1247 : : {
1248 [ - + ]: 56 : if (ps->dropped)
1249 : 0 : continue;
1250 : :
1251 [ - + - - ]: 56 : if (do_drop != NULL && !do_drop(ps, match_data))
1252 : 0 : continue;
1253 : :
1254 : : /* delete local reference */
1255 [ - + ]: 56 : if (pgStatEntryRefHash)
1256 : : {
1257 : : PgStat_EntryRefHashEntry *lohashent =
1258 : 0 : pgstat_entry_ref_hash_lookup(pgStatEntryRefHash, ps->key);
1259 : :
1260 [ # # ]: 0 : if (lohashent)
1261 : 0 : pgstat_release_entry_ref(lohashent->key, lohashent->entry_ref,
1262 : : true);
1263 : : }
1264 : :
1265 [ - + ]: 56 : if (!pgstat_drop_entry_internal(ps, &hstat))
1266 : 0 : not_freed_count++;
1267 : : }
1268 : 271 : dshash_seq_term(&hstat);
1269 : : }
1270 : :
1271 [ - + ]: 270 : if (not_freed_count > 0)
1272 : 0 : pgstat_request_entry_refs_gc();
1273 : 270 : }
1274 : :
1275 : : /*
1276 : : * Scan through the stats hashtables and drop all entries.
1277 : : */
1278 : : void
1279 : 270 : pgstat_drop_all_entries(void)
1280 : : {
1281 : 270 : pgstat_drop_matching_entries(NULL, 0);
1282 : 270 : }
1283 : :
1284 : : static void
1285 : 12212 : shared_stat_reset_contents(PgStat_Kind kind, PgStatShared_Common *header,
1286 : : TimestampTz ts)
1287 : : {
1288 : 12212 : const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
1289 : :
1290 : 12212 : memset(pgstat_get_entry_data(kind, header), 0,
1291 : : pgstat_get_entry_len(kind));
1292 : :
1293 [ + + ]: 12212 : if (kind_info->reset_timestamp_cb)
1294 : 12208 : kind_info->reset_timestamp_cb(header, ts);
1295 : 12212 : }
1296 : :
1297 : : static void
1298 : 21 : pgstat_reset_matching_entries_in_hash(dshash_table *hash,
1299 : : bool (*do_reset) (PgStatShared_HashEntry *, Datum),
1300 : : Datum match_data,
1301 : : TimestampTz ts)
1302 : : {
1303 : : dshash_seq_status hstat;
1304 : : PgStatShared_HashEntry *p;
1305 : :
1306 : : /* dshash entry is not modified, take shared lock */
1307 : 21 : dshash_seq_init(&hstat, hash, false);
1308 [ + + ]: 17080 : while ((p = dshash_seq_next(&hstat)) != NULL)
1309 : : {
1310 : : PgStatShared_Common *header;
1311 : :
1312 [ + + ]: 17059 : if (p->dropped)
1313 : 1 : continue;
1314 : :
1315 [ + + ]: 17058 : if (!do_reset(p, match_data))
1316 : 5108 : continue;
1317 : :
1318 : 11950 : header = dsa_get_address(pgStatLocal.kind_dsa[p->key.kind], p->body);
1319 : :
1320 : 11950 : LWLockAcquire(&header->lock, LW_EXCLUSIVE);
1321 : :
1322 : 11950 : shared_stat_reset_contents(p->key.kind, header, ts);
1323 : :
1324 : 11950 : LWLockRelease(&header->lock);
1325 : : }
1326 : 21 : dshash_seq_term(&hstat);
1327 : 21 : }
1328 : :
1329 : : /*
1330 : : * Reset one variable-numbered stats entry.
1331 : : */
1332 : : void
1333 : 263 : pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts)
1334 : : {
1335 : : PgStat_EntryRef *entry_ref;
1336 : :
1337 : : Assert(!pgstat_get_kind_info(kind)->fixed_amount);
1338 : :
1339 : 263 : entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL);
1340 [ + + - + ]: 263 : if (!entry_ref || entry_ref->shared_entry->dropped)
1341 : 1 : return;
1342 : :
1343 : 262 : (void) pgstat_lock_entry(entry_ref, false);
1344 : 262 : shared_stat_reset_contents(kind, entry_ref->shared_stats, ts);
1345 : 262 : pgstat_unlock_entry(entry_ref);
1346 : : }
1347 : :
1348 : : /*
1349 : : * Scan through the stats hashtables, resetting statistics if approved by the
1350 : : * provided do_reset() function.
1351 : : */
1352 : : void
1353 : 15 : pgstat_reset_matching_entries(bool (*do_reset) (PgStatShared_HashEntry *, Datum),
1354 : : Datum match_data, TimestampTz ts)
1355 : : {
1356 [ + + ]: 30 : for (int h = 0; h < pgStatLocal.num_var_hashes; h++)
1357 : : {
1358 : 15 : pgstat_reset_matching_entries_in_hash(pgStatLocal.var_hashes[h],
1359 : : do_reset,
1360 : : match_data,
1361 : : ts);
1362 : : }
1363 : 15 : }
1364 : :
1365 : : static bool
1366 : 1548 : match_kind(PgStatShared_HashEntry *p, Datum match_data)
1367 : : {
1368 : 1548 : return p->key.kind == DatumGetInt32(match_data);
1369 : : }
1370 : :
1371 : : void
1372 : 6 : pgstat_reset_entries_of_kind(PgStat_Kind kind, TimestampTz ts)
1373 : : {
1374 : : Assert(pgStatLocal.kind_hash[kind] != NULL);
1375 : :
1376 : 6 : pgstat_reset_matching_entries_in_hash(pgStatLocal.kind_hash[kind],
1377 : : match_kind,
1378 : : Int32GetDatum(kind),
1379 : : ts);
1380 : 6 : }
1381 : :
1382 : : static void
1383 : 4550005 : pgstat_setup_memcxt(void)
1384 : : {
1385 [ + + ]: 4550005 : if (unlikely(!pgStatSharedRefContext))
1386 : 22493 : pgStatSharedRefContext =
1387 : 22493 : AllocSetContextCreate(TopMemoryContext,
1388 : : "PgStat Shared Ref",
1389 : : ALLOCSET_SMALL_SIZES);
1390 [ + + ]: 4550005 : if (unlikely(!pgStatEntryRefHashContext))
1391 : 22493 : pgStatEntryRefHashContext =
1392 : 22493 : AllocSetContextCreate(TopMemoryContext,
1393 : : "PgStat Shared Ref Hash",
1394 : : ALLOCSET_SMALL_SIZES);
1395 : 4550005 : }
|