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