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