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