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-2024, 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 pgStatShmLookupCache */
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
739 andres@anarazel.de 106 :CBC 5271 : 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 : 5271 : sz = 256 * 1024;
119 [ - + ]: 5271 : Assert(dsa_minimum_size() <= sz);
120 : 5271 : return MAXALIGN(sz);
121 : : }
122 : :
123 : : /*
124 : : * Compute shared memory space needed for cumulative statistics
125 : : */
126 : : Size
127 : 2577 : StatsShmemSize(void)
128 : : {
129 : : Size sz;
130 : :
131 : 2577 : sz = MAXALIGN(sizeof(PgStat_ShmemControl));
132 : 2577 : sz = add_size(sz, pgstat_dsa_init_size());
133 : :
134 : 2577 : return sz;
135 : : }
136 : :
137 : : /*
138 : : * Initialize cumulative statistics system during startup
139 : : */
140 : : void
141 : 898 : StatsShmemInit(void)
142 : : {
143 : : bool found;
144 : : Size sz;
145 : :
146 : 898 : sz = StatsShmemSize();
147 : 898 : pgStatLocal.shmem = (PgStat_ShmemControl *)
148 : 898 : ShmemInitStruct("Shared Memory Stats", sz, &found);
149 : :
150 [ + - ]: 898 : if (!IsUnderPostmaster)
151 : : {
152 : : dsa_area *dsa;
153 : : dshash_table *dsh;
154 : 898 : PgStat_ShmemControl *ctl = pgStatLocal.shmem;
155 : 898 : char *p = (char *) ctl;
156 : :
157 [ - + ]: 898 : Assert(!found);
158 : :
159 : : /* the allocation of pgStatLocal.shmem itself */
160 : 898 : p += MAXALIGN(sizeof(PgStat_ShmemControl));
161 : :
162 : : /*
163 : : * Create a small dsa allocation in plain shared memory. This is
164 : : * required because postmaster cannot use dsm segments. It also
165 : : * provides a small efficiency win.
166 : : */
167 : 898 : ctl->raw_dsa_area = p;
168 : 898 : p += MAXALIGN(pgstat_dsa_init_size());
169 : 898 : dsa = dsa_create_in_place(ctl->raw_dsa_area,
170 : : pgstat_dsa_init_size(),
171 : : LWTRANCHE_PGSTATS_DSA, 0);
172 : 898 : dsa_pin(dsa);
173 : :
174 : : /*
175 : : * To ensure dshash is created in "plain" shared memory, temporarily
176 : : * limit size of dsa to the initial size of the dsa.
177 : : */
178 : 898 : dsa_set_size_limit(dsa, pgstat_dsa_init_size());
179 : :
180 : : /*
181 : : * With the limit in place, create the dshash table. XXX: It'd be nice
182 : : * if there were dshash_create_in_place().
183 : : */
48 nathan@postgresql.or 184 :GNC 898 : dsh = dshash_create(dsa, &dsh_params, NULL);
739 andres@anarazel.de 185 :CBC 898 : ctl->hash_handle = dshash_get_hash_table_handle(dsh);
186 : :
187 : : /* lift limit set above */
188 : 898 : dsa_set_size_limit(dsa, -1);
189 : :
190 : : /*
191 : : * Postmaster will never access these again, thus free the local
192 : : * dsa/dshash references.
193 : : */
194 : 898 : dshash_detach(dsh);
195 : 898 : dsa_detach(dsa);
196 : :
197 : 898 : pg_atomic_init_u64(&ctl->gc_request_count, 1);
198 : :
199 : :
200 : : /* initialize fixed-numbered stats */
201 : 898 : LWLockInitialize(&ctl->archiver.lock, LWTRANCHE_PGSTATS_DATA);
202 : 898 : LWLockInitialize(&ctl->bgwriter.lock, LWTRANCHE_PGSTATS_DATA);
203 : 898 : LWLockInitialize(&ctl->checkpointer.lock, LWTRANCHE_PGSTATS_DATA);
204 : 898 : LWLockInitialize(&ctl->slru.lock, LWTRANCHE_PGSTATS_DATA);
205 : 898 : LWLockInitialize(&ctl->wal.lock, LWTRANCHE_PGSTATS_DATA);
206 : :
431 207 [ + + ]: 15266 : for (int i = 0; i < BACKEND_NUM_TYPES; i++)
208 : 14368 : LWLockInitialize(&ctl->io.locks[i],
209 : : LWTRANCHE_PGSTATS_DATA);
210 : : }
211 : : else
212 : : {
739 andres@anarazel.de 213 [ # # ]:UBC 0 : Assert(found);
214 : : }
739 andres@anarazel.de 215 :CBC 898 : }
216 : :
217 : : void
218 : 19579 : pgstat_attach_shmem(void)
219 : : {
220 : : MemoryContext oldcontext;
221 : :
222 [ - + ]: 19579 : Assert(pgStatLocal.dsa == NULL);
223 : :
224 : : /* stats shared memory persists for the backend lifetime */
225 : 19579 : oldcontext = MemoryContextSwitchTo(TopMemoryContext);
226 : :
227 : 19579 : pgStatLocal.dsa = dsa_attach_in_place(pgStatLocal.shmem->raw_dsa_area,
228 : : NULL);
229 : 19579 : dsa_pin_mapping(pgStatLocal.dsa);
230 : :
231 : 39158 : pgStatLocal.shared_hash = dshash_attach(pgStatLocal.dsa, &dsh_params,
232 : 19579 : pgStatLocal.shmem->hash_handle, 0);
233 : :
234 : 19579 : MemoryContextSwitchTo(oldcontext);
235 : 19579 : }
236 : :
237 : : void
238 : 18049 : pgstat_detach_shmem(void)
239 : : {
240 [ - + ]: 18049 : Assert(pgStatLocal.dsa);
241 : :
242 : : /* we shouldn't leave references to shared stats */
243 : 18049 : pgstat_release_all_entry_refs(false);
244 : :
245 : 18049 : dshash_detach(pgStatLocal.shared_hash);
246 : 18049 : pgStatLocal.shared_hash = NULL;
247 : :
248 : 18049 : dsa_detach(pgStatLocal.dsa);
249 : 18049 : pgStatLocal.dsa = NULL;
250 : 18049 : }
251 : :
252 : :
253 : : /* ------------------------------------------------------------
254 : : * Maintenance of shared memory stats entries
255 : : * ------------------------------------------------------------
256 : : */
257 : :
258 : : PgStatShared_Common *
259 : 242713 : pgstat_init_entry(PgStat_Kind kind,
260 : : PgStatShared_HashEntry *shhashent)
261 : : {
262 : : /* Create new stats entry. */
263 : : dsa_pointer chunk;
264 : : PgStatShared_Common *shheader;
265 : :
266 : : /*
267 : : * Initialize refcount to 1, marking it as valid / not dropped. The entry
268 : : * can't be freed before the initialization because it can't be found as
269 : : * long as we hold the dshash partition lock. Caller needs to increase
270 : : * further if a longer lived reference is needed.
271 : : */
272 : 242713 : pg_atomic_init_u32(&shhashent->refcount, 1);
273 : 242713 : shhashent->dropped = false;
274 : :
275 : 242713 : chunk = dsa_allocate0(pgStatLocal.dsa, pgstat_get_kind_info(kind)->shared_size);
276 : 242713 : shheader = dsa_get_address(pgStatLocal.dsa, chunk);
277 : 242713 : shheader->magic = 0xdeadbeef;
278 : :
279 : : /* Link the new entry from the hash entry. */
280 : 242713 : shhashent->body = chunk;
281 : :
282 : 242713 : LWLockInitialize(&shheader->lock, LWTRANCHE_PGSTATS_DATA);
283 : :
284 : 242713 : return shheader;
285 : : }
286 : :
287 : : static PgStatShared_Common *
288 : 26 : pgstat_reinit_entry(PgStat_Kind kind, PgStatShared_HashEntry *shhashent)
289 : : {
290 : : PgStatShared_Common *shheader;
291 : :
292 : 26 : shheader = dsa_get_address(pgStatLocal.dsa, shhashent->body);
293 : :
294 : : /* mark as not dropped anymore */
295 : 26 : pg_atomic_fetch_add_u32(&shhashent->refcount, 1);
296 : 26 : shhashent->dropped = false;
297 : :
298 : : /* reinitialize content */
299 [ - + ]: 26 : Assert(shheader->magic == 0xdeadbeef);
300 : 26 : memset(pgstat_get_entry_data(kind, shheader), 0,
301 : : pgstat_get_entry_len(kind));
302 : :
303 : 26 : return shheader;
304 : : }
305 : :
306 : : static void
307 : 2037741 : pgstat_setup_shared_refs(void)
308 : : {
309 [ + + ]: 2037741 : if (likely(pgStatEntryRefHash != NULL))
310 : 2021981 : return;
311 : :
312 : 15760 : pgStatEntryRefHash =
313 : 15760 : pgstat_entry_ref_hash_create(pgStatEntryRefHashContext,
314 : : PGSTAT_ENTRY_REF_HASH_SIZE, NULL);
315 : 15760 : pgStatSharedRefAge = pg_atomic_read_u64(&pgStatLocal.shmem->gc_request_count);
316 [ - + ]: 15760 : Assert(pgStatSharedRefAge != 0);
317 : : }
318 : :
319 : : /*
320 : : * Helper function for pgstat_get_entry_ref().
321 : : */
322 : : static void
323 : 761700 : pgstat_acquire_entry_ref(PgStat_EntryRef *entry_ref,
324 : : PgStatShared_HashEntry *shhashent,
325 : : PgStatShared_Common *shheader)
326 : : {
327 [ - + ]: 761700 : Assert(shheader->magic == 0xdeadbeef);
328 [ - + ]: 761700 : Assert(pg_atomic_read_u32(&shhashent->refcount) > 0);
329 : :
330 : 761700 : pg_atomic_fetch_add_u32(&shhashent->refcount, 1);
331 : :
332 : 761700 : dshash_release_lock(pgStatLocal.shared_hash, shhashent);
333 : :
334 : 761700 : entry_ref->shared_stats = shheader;
335 : 761700 : entry_ref->shared_entry = shhashent;
336 : 761700 : }
337 : :
338 : : /*
339 : : * Helper function for pgstat_get_entry_ref().
340 : : */
341 : : static bool
342 : 2037741 : pgstat_get_entry_ref_cached(PgStat_HashKey key, PgStat_EntryRef **entry_ref_p)
343 : : {
344 : : bool found;
345 : : PgStat_EntryRefHashEntry *cache_entry;
346 : :
347 : : /*
348 : : * We immediately insert a cache entry, because it avoids 1) multiple
349 : : * hashtable lookups in case of a cache miss 2) having to deal with
350 : : * out-of-memory errors after incrementing PgStatShared_Common->refcount.
351 : : */
352 : :
353 : 2037741 : cache_entry = pgstat_entry_ref_hash_insert(pgStatEntryRefHash, key, &found);
354 : :
355 [ + + - + ]: 2037741 : if (!found || !cache_entry->entry_ref)
356 : 833474 : {
357 : : PgStat_EntryRef *entry_ref;
358 : :
359 : 833474 : cache_entry->entry_ref = entry_ref =
360 : 833474 : MemoryContextAlloc(pgStatSharedRefContext,
361 : : sizeof(PgStat_EntryRef));
362 : 833474 : entry_ref->shared_stats = NULL;
363 : 833474 : entry_ref->shared_entry = NULL;
364 : 833474 : entry_ref->pending = NULL;
365 : :
366 : 833474 : found = false;
367 : : }
368 [ - + ]: 1204267 : else if (cache_entry->entry_ref->shared_stats == NULL)
369 : : {
739 andres@anarazel.de 370 [ # # ]:UBC 0 : Assert(cache_entry->entry_ref->pending == NULL);
371 : 0 : found = false;
372 : : }
373 : : else
374 : : {
375 : : PgStat_EntryRef *entry_ref PG_USED_FOR_ASSERTS_ONLY;
376 : :
739 andres@anarazel.de 377 :CBC 1204267 : entry_ref = cache_entry->entry_ref;
378 [ - + ]: 1204267 : Assert(entry_ref->shared_entry != NULL);
379 [ - + ]: 1204267 : Assert(entry_ref->shared_stats != NULL);
380 : :
381 [ - + ]: 1204267 : Assert(entry_ref->shared_stats->magic == 0xdeadbeef);
382 : : /* should have at least our reference */
383 [ - + ]: 1204267 : Assert(pg_atomic_read_u32(&entry_ref->shared_entry->refcount) > 0);
384 : : }
385 : :
386 : 2037741 : *entry_ref_p = cache_entry->entry_ref;
387 : 2037741 : return found;
388 : : }
389 : :
390 : : /*
391 : : * Get a shared stats reference. If create is true, the shared stats object is
392 : : * created if it does not exist.
393 : : *
394 : : * When create is true, and created_entry is non-NULL, it'll be set to true
395 : : * if the entry is newly created, false otherwise.
396 : : */
397 : : PgStat_EntryRef *
398 : 2037741 : pgstat_get_entry_ref(PgStat_Kind kind, Oid dboid, Oid objoid, bool create,
399 : : bool *created_entry)
400 : : {
401 : 2037741 : PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objoid = objoid};
402 : : PgStatShared_HashEntry *shhashent;
403 : 2037741 : PgStatShared_Common *shheader = NULL;
404 : : PgStat_EntryRef *entry_ref;
405 : :
406 : : /*
407 : : * passing in created_entry only makes sense if we possibly could create
408 : : * entry.
409 : : */
534 peter@eisentraut.org 410 [ + + - + ]: 2037741 : Assert(create || created_entry == NULL);
739 andres@anarazel.de 411 : 2037741 : pgstat_assert_is_up();
412 [ - + ]: 2037741 : Assert(pgStatLocal.shared_hash != NULL);
413 [ - + ]: 2037741 : Assert(!pgStatLocal.shmem->is_shutdown);
414 : :
415 : 2037741 : pgstat_setup_memcxt();
416 : 2037741 : pgstat_setup_shared_refs();
417 : :
418 [ + + ]: 2037741 : if (created_entry != NULL)
419 : 107 : *created_entry = false;
420 : :
421 : : /*
422 : : * Check if other backends dropped stats that could not be deleted because
423 : : * somebody held references to it. If so, check this backend's references.
424 : : * This is not expected to happen often. The location of the check is a
425 : : * bit random, but this is a relatively frequently called path, so better
426 : : * than most.
427 : : */
428 [ + + ]: 2037741 : if (pgstat_need_entry_refs_gc())
429 : 5734 : pgstat_gc_entry_refs();
430 : :
431 : : /*
432 : : * First check the lookup cache hashtable in local memory. If we find a
433 : : * match here we can avoid taking locks / causing contention.
434 : : */
435 [ + + ]: 2037741 : if (pgstat_get_entry_ref_cached(key, &entry_ref))
436 : 1204267 : return entry_ref;
437 : :
438 [ - + ]: 833474 : Assert(entry_ref != NULL);
439 : :
440 : : /*
441 : : * Do a lookup in the hash table first - it's quite likely that the entry
442 : : * already exists, and that way we only need a shared lock.
443 : : */
444 : 833474 : shhashent = dshash_find(pgStatLocal.shared_hash, &key, false);
445 : :
446 [ + + + + ]: 833474 : if (create && !shhashent)
447 : : {
448 : : bool shfound;
449 : :
450 : : /*
451 : : * It's possible that somebody created the entry since the above
452 : : * lookup. If so, fall through to the same path as if we'd have if it
453 : : * already had been created before the dshash_find() calls.
454 : : */
455 : 85280 : shhashent = dshash_find_or_insert(pgStatLocal.shared_hash, &key, &shfound);
456 [ + - ]: 85280 : if (!shfound)
457 : : {
458 : 85280 : shheader = pgstat_init_entry(kind, shhashent);
459 : 85280 : pgstat_acquire_entry_ref(entry_ref, shhashent, shheader);
460 : :
461 [ + + ]: 85280 : if (created_entry != NULL)
462 : 48 : *created_entry = true;
463 : :
464 : 85280 : return entry_ref;
465 : : }
466 : : }
467 : :
468 [ + + ]: 748194 : if (!shhashent)
469 : : {
470 : : /*
471 : : * If we're not creating, delete the reference again. In all
472 : : * likelihood it's just a stats lookup - no point wasting memory for a
473 : : * shared ref to nothing...
474 : : */
475 : 71737 : pgstat_release_entry_ref(key, entry_ref, false);
476 : :
477 : 71737 : return NULL;
478 : : }
479 : : else
480 : : {
481 : : /*
482 : : * Can get here either because dshash_find() found a match, or if
483 : : * dshash_find_or_insert() found a concurrently inserted entry.
484 : : */
485 : :
486 [ + + + + ]: 676457 : if (shhashent->dropped && create)
487 : : {
488 : : /*
489 : : * There are legitimate cases where the old stats entry might not
490 : : * yet have been dropped by the time it's reused. The most obvious
491 : : * case are replication slot stats, where a new slot can be
492 : : * created with the same index just after dropping. But oid
493 : : * wraparound can lead to other cases as well. We just reset the
494 : : * stats to their plain state.
495 : : */
496 : 26 : shheader = pgstat_reinit_entry(kind, shhashent);
497 : 26 : pgstat_acquire_entry_ref(entry_ref, shhashent, shheader);
498 : :
499 [ - + ]: 26 : if (created_entry != NULL)
739 andres@anarazel.de 500 :UBC 0 : *created_entry = true;
501 : :
739 andres@anarazel.de 502 :CBC 26 : return entry_ref;
503 : : }
504 [ + + ]: 676431 : else if (shhashent->dropped)
505 : : {
506 : 37 : dshash_release_lock(pgStatLocal.shared_hash, shhashent);
507 : 37 : pgstat_release_entry_ref(key, entry_ref, false);
508 : :
509 : 37 : return NULL;
510 : : }
511 : : else
512 : : {
513 : 676394 : shheader = dsa_get_address(pgStatLocal.dsa, shhashent->body);
514 : 676394 : pgstat_acquire_entry_ref(entry_ref, shhashent, shheader);
515 : :
516 : 676394 : return entry_ref;
517 : : }
518 : : }
519 : : }
520 : :
521 : : static void
522 : 830830 : pgstat_release_entry_ref(PgStat_HashKey key, PgStat_EntryRef *entry_ref,
523 : : bool discard_pending)
524 : : {
525 [ + - + + ]: 830830 : if (entry_ref && entry_ref->pending)
526 : : {
527 [ + - ]: 30662 : if (discard_pending)
528 : 30662 : pgstat_delete_pending_entry(entry_ref);
529 : : else
739 andres@anarazel.de 530 [ # # ]:UBC 0 : elog(ERROR, "releasing ref with pending data");
531 : : }
532 : :
739 andres@anarazel.de 533 [ + - + + ]:CBC 830830 : if (entry_ref && entry_ref->shared_stats)
534 : : {
535 [ - + ]: 759056 : Assert(entry_ref->shared_stats->magic == 0xdeadbeef);
536 [ - + ]: 759056 : Assert(entry_ref->pending == NULL);
537 : :
538 : : /*
539 : : * This can't race with another backend looking up the stats entry and
540 : : * increasing the refcount because it is not "legal" to create
541 : : * additional references to dropped entries.
542 : : */
543 [ + + ]: 759056 : if (pg_atomic_fetch_sub_u32(&entry_ref->shared_entry->refcount, 1) == 1)
544 : : {
545 : : PgStatShared_HashEntry *shent;
546 : :
547 : : /*
548 : : * We're the last referrer to this entry, try to drop the shared
549 : : * entry.
550 : : */
551 : :
552 : : /* only dropped entries can reach a 0 refcount */
553 [ - + ]: 4828 : Assert(entry_ref->shared_entry->dropped);
554 : :
555 : 4828 : shent = dshash_find(pgStatLocal.shared_hash,
556 : 4828 : &entry_ref->shared_entry->key,
557 : : true);
558 [ - + ]: 4828 : if (!shent)
739 andres@anarazel.de 559 [ # # ]:UBC 0 : elog(ERROR, "could not find just referenced shared stats entry");
560 : :
739 andres@anarazel.de 561 [ - + ]:CBC 4828 : Assert(pg_atomic_read_u32(&entry_ref->shared_entry->refcount) == 0);
562 [ - + ]: 4828 : Assert(entry_ref->shared_entry == shent);
563 : :
564 : 4828 : pgstat_free_entry(shent, NULL);
565 : : }
566 : : }
567 : :
568 [ - + ]: 830830 : if (!pgstat_entry_ref_hash_delete(pgStatEntryRefHash, key))
739 andres@anarazel.de 569 [ # # ]:UBC 0 : elog(ERROR, "entry ref vanished before deletion");
570 : :
739 andres@anarazel.de 571 [ + - ]:CBC 830830 : if (entry_ref)
572 : 830830 : pfree(entry_ref);
573 : 830830 : }
574 : :
575 : : bool
576 : 934928 : pgstat_lock_entry(PgStat_EntryRef *entry_ref, bool nowait)
577 : : {
578 : 934928 : LWLock *lock = &entry_ref->shared_stats->lock;
579 : :
580 [ + + ]: 934928 : if (nowait)
581 : 313947 : return LWLockConditionalAcquire(lock, LW_EXCLUSIVE);
582 : :
583 : 620981 : LWLockAcquire(lock, LW_EXCLUSIVE);
584 : 620981 : return true;
585 : : }
586 : :
587 : : /*
588 : : * Separate from pgstat_lock_entry() as most callers will need to lock
589 : : * exclusively.
590 : : */
591 : : bool
601 592 : 166883 : pgstat_lock_entry_shared(PgStat_EntryRef *entry_ref, bool nowait)
593 : : {
594 : 166883 : LWLock *lock = &entry_ref->shared_stats->lock;
595 : :
596 [ - + ]: 166883 : if (nowait)
601 andres@anarazel.de 597 :UBC 0 : return LWLockConditionalAcquire(lock, LW_SHARED);
598 : :
601 andres@anarazel.de 599 :CBC 166883 : LWLockAcquire(lock, LW_SHARED);
600 : 166883 : return true;
601 : : }
602 : :
603 : : void
739 604 : 1101782 : pgstat_unlock_entry(PgStat_EntryRef *entry_ref)
605 : : {
606 : 1101782 : LWLockRelease(&entry_ref->shared_stats->lock);
607 : 1101782 : }
608 : :
609 : : /*
610 : : * Helper function to fetch and lock shared stats.
611 : : */
612 : : PgStat_EntryRef *
613 : 96490 : pgstat_get_entry_ref_locked(PgStat_Kind kind, Oid dboid, Oid objoid,
614 : : bool nowait)
615 : : {
616 : : PgStat_EntryRef *entry_ref;
617 : :
618 : : /* find shared table stats entry corresponding to the local entry */
619 : 96490 : entry_ref = pgstat_get_entry_ref(kind, dboid, objoid, true, NULL);
620 : :
621 : : /* lock the shared entry to protect the content, skip if failed */
622 [ - + ]: 96490 : if (!pgstat_lock_entry(entry_ref, nowait))
739 andres@anarazel.de 623 :UBC 0 : return NULL;
624 : :
739 andres@anarazel.de 625 :CBC 96490 : return entry_ref;
626 : : }
627 : :
628 : : void
629 : 1829 : pgstat_request_entry_refs_gc(void)
630 : : {
631 : 1829 : pg_atomic_fetch_add_u64(&pgStatLocal.shmem->gc_request_count, 1);
632 : 1829 : }
633 : :
634 : : static bool
635 : 2037741 : pgstat_need_entry_refs_gc(void)
636 : : {
637 : : uint64 curage;
638 : :
639 [ - + ]: 2037741 : if (!pgStatEntryRefHash)
739 andres@anarazel.de 640 :UBC 0 : return false;
641 : :
642 : : /* should have been initialized when creating pgStatEntryRefHash */
739 andres@anarazel.de 643 [ - + ]:CBC 2037741 : Assert(pgStatSharedRefAge != 0);
644 : :
645 : 2037741 : curage = pg_atomic_read_u64(&pgStatLocal.shmem->gc_request_count);
646 : :
647 : 2037741 : return pgStatSharedRefAge != curage;
648 : : }
649 : :
650 : : static void
651 : 5734 : pgstat_gc_entry_refs(void)
652 : : {
653 : : pgstat_entry_ref_hash_iterator i;
654 : : PgStat_EntryRefHashEntry *ent;
655 : : uint64 curage;
656 : :
657 : 5734 : curage = pg_atomic_read_u64(&pgStatLocal.shmem->gc_request_count);
658 [ - + ]: 5734 : Assert(curage != 0);
659 : :
660 : : /*
661 : : * Some entries have been dropped. Invalidate cache pointer to them.
662 : : */
663 : 5734 : pgstat_entry_ref_hash_start_iterate(pgStatEntryRefHash, &i);
664 [ + + ]: 402732 : while ((ent = pgstat_entry_ref_hash_iterate(pgStatEntryRefHash, &i)) != NULL)
665 : : {
666 : 396998 : PgStat_EntryRef *entry_ref = ent->entry_ref;
667 : :
668 [ + - - + ]: 396998 : Assert(!entry_ref->shared_stats ||
669 : : entry_ref->shared_stats->magic == 0xdeadbeef);
670 : :
671 [ + + ]: 396998 : if (!entry_ref->shared_entry->dropped)
672 : 318181 : continue;
673 : :
674 : : /* cannot gc shared ref that has pending data */
675 [ + + ]: 78817 : if (entry_ref->pending != NULL)
676 : 73157 : continue;
677 : :
678 : 5660 : pgstat_release_entry_ref(ent->key, entry_ref, false);
679 : : }
680 : :
681 : 5734 : pgStatSharedRefAge = curage;
682 : 5734 : }
683 : :
684 : : static void
685 : 15225 : pgstat_release_matching_entry_refs(bool discard_pending, ReleaseMatchCB match,
686 : : Datum match_data)
687 : : {
688 : : pgstat_entry_ref_hash_iterator i;
689 : : PgStat_EntryRefHashEntry *ent;
690 : :
691 [ + + ]: 15225 : if (pgStatEntryRefHash == NULL)
692 : 6 : return;
693 : :
694 : 15219 : pgstat_entry_ref_hash_start_iterate(pgStatEntryRefHash, &i);
695 : :
696 : 738081 : while ((ent = pgstat_entry_ref_hash_iterate(pgStatEntryRefHash, &i))
697 [ + + ]: 738081 : != NULL)
698 : : {
699 [ - + ]: 722862 : Assert(ent->entry_ref != NULL);
700 : :
701 [ + + + - ]: 722862 : if (match && !match(ent, match_data))
702 : 510 : continue;
703 : :
704 : 722352 : pgstat_release_entry_ref(ent->key, ent->entry_ref, discard_pending);
705 : : }
706 : : }
707 : :
708 : : /*
709 : : * Release all local references to shared stats entries.
710 : : *
711 : : * When a process exits it cannot do so while still holding references onto
712 : : * stats entries, otherwise the shared stats entries could never be freed.
713 : : */
714 : : static void
715 : 18049 : pgstat_release_all_entry_refs(bool discard_pending)
716 : : {
717 [ + + ]: 18049 : if (pgStatEntryRefHash == NULL)
718 : 2846 : return;
719 : :
720 : 15203 : pgstat_release_matching_entry_refs(discard_pending, NULL, 0);
721 [ - + ]: 15203 : Assert(pgStatEntryRefHash->members == 0);
722 : 15203 : pgstat_entry_ref_hash_destroy(pgStatEntryRefHash);
723 : 15203 : pgStatEntryRefHash = NULL;
724 : : }
725 : :
726 : : static bool
727 : 510 : match_db(PgStat_EntryRefHashEntry *ent, Datum match_data)
728 : : {
729 : 510 : Oid dboid = DatumGetObjectId(match_data);
730 : :
731 : 510 : return ent->key.dboid == dboid;
732 : : }
733 : :
734 : : static void
735 : 22 : pgstat_release_db_entry_refs(Oid dboid)
736 : : {
737 : 22 : pgstat_release_matching_entry_refs( /* discard pending = */ true,
738 : : match_db,
739 : : ObjectIdGetDatum(dboid));
740 : 22 : }
741 : :
742 : :
743 : : /* ------------------------------------------------------------
744 : : * Dropping and resetting of stats entries
745 : : * ------------------------------------------------------------
746 : : */
747 : :
748 : : static void
749 : 33174 : pgstat_free_entry(PgStatShared_HashEntry *shent, dshash_seq_status *hstat)
750 : : {
751 : : dsa_pointer pdsa;
752 : :
753 : : /*
754 : : * Fetch dsa pointer before deleting entry - that way we can free the
755 : : * memory after releasing the lock.
756 : : */
757 : 33174 : pdsa = shent->body;
758 : :
759 [ + + ]: 33174 : if (!hstat)
760 : 31130 : dshash_delete_entry(pgStatLocal.shared_hash, shent);
761 : : else
762 : 2044 : dshash_delete_current(hstat);
763 : :
764 : 33174 : dsa_free(pgStatLocal.dsa, pdsa);
765 : 33174 : }
766 : :
767 : : /*
768 : : * Helper for both pgstat_drop_database_and_contents() and
769 : : * pgstat_drop_entry(). If hstat is non-null delete the shared entry using
770 : : * dshash_delete_current(), otherwise use dshash_delete_entry(). In either
771 : : * case the entry needs to be already locked.
772 : : */
773 : : static bool
774 : 33201 : pgstat_drop_entry_internal(PgStatShared_HashEntry *shent,
775 : : dshash_seq_status *hstat)
776 : : {
777 [ - + ]: 33201 : Assert(shent->body != InvalidDsaPointer);
778 : :
779 : : /* should already have released local reference */
780 [ + + ]: 33201 : if (pgStatEntryRefHash)
781 [ - + ]: 32951 : Assert(!pgstat_entry_ref_hash_lookup(pgStatEntryRefHash, shent->key));
782 : :
783 : : /*
784 : : * Signal that the entry is dropped - this will eventually cause other
785 : : * backends to release their references.
786 : : */
787 [ - + ]: 33201 : if (shent->dropped)
739 andres@anarazel.de 788 [ # # ]:UBC 0 : elog(ERROR, "can only drop stats once");
739 andres@anarazel.de 789 :CBC 33201 : shent->dropped = true;
790 : :
791 : : /* release refcount marking entry as not dropped */
792 [ + + ]: 33201 : if (pg_atomic_sub_fetch_u32(&shent->refcount, 1) == 0)
793 : : {
794 : 28346 : pgstat_free_entry(shent, hstat);
795 : 28346 : return true;
796 : : }
797 : : else
798 : : {
799 [ + - ]: 4855 : if (!hstat)
800 : 4855 : dshash_release_lock(pgStatLocal.shared_hash, shent);
801 : 4855 : return false;
802 : : }
803 : : }
804 : :
805 : : /*
806 : : * Drop stats for the database and all the objects inside that database.
807 : : */
808 : : static void
809 : 22 : pgstat_drop_database_and_contents(Oid dboid)
810 : : {
811 : : dshash_seq_status hstat;
812 : : PgStatShared_HashEntry *p;
813 : 22 : uint64 not_freed_count = 0;
814 : :
815 [ - + ]: 22 : Assert(OidIsValid(dboid));
816 : :
817 [ - + ]: 22 : Assert(pgStatLocal.shared_hash != NULL);
818 : :
819 : : /*
820 : : * This backend might very well be the only backend holding a reference to
821 : : * about-to-be-dropped entries. Ensure that we're not preventing it from
822 : : * being cleaned up till later.
823 : : *
824 : : * Doing this separately from the dshash iteration below avoids having to
825 : : * do so while holding a partition lock on the shared hashtable.
826 : : */
827 : 22 : pgstat_release_db_entry_refs(dboid);
828 : :
829 : : /* some of the dshash entries are to be removed, take exclusive lock. */
830 : 22 : dshash_seq_init(&hstat, pgStatLocal.shared_hash, true);
831 [ + + ]: 7558 : while ((p = dshash_seq_next(&hstat)) != NULL)
832 : : {
833 [ + + ]: 7536 : if (p->dropped)
834 : 1 : continue;
835 : :
836 [ + + ]: 7535 : if (p->key.dboid != dboid)
837 : 5547 : continue;
838 : :
839 [ - + ]: 1988 : if (!pgstat_drop_entry_internal(p, &hstat))
840 : : {
841 : : /*
842 : : * Even statistics for a dropped database might currently be
843 : : * accessed (consider e.g. database stats for pg_stat_database).
844 : : */
739 andres@anarazel.de 845 :UBC 0 : not_freed_count++;
846 : : }
847 : : }
739 andres@anarazel.de 848 :CBC 22 : dshash_seq_term(&hstat);
849 : :
850 : : /*
851 : : * If some of the stats data could not be freed, signal the reference
852 : : * holders to run garbage collection of their cached pgStatShmLookupCache.
853 : : */
854 [ - + ]: 22 : if (not_freed_count > 0)
739 andres@anarazel.de 855 :UBC 0 : pgstat_request_entry_refs_gc();
739 andres@anarazel.de 856 :CBC 22 : }
857 : :
858 : : bool
859 : 46452 : pgstat_drop_entry(PgStat_Kind kind, Oid dboid, Oid objoid)
860 : : {
861 : 46452 : PgStat_HashKey key = {.kind = kind,.dboid = dboid,.objoid = objoid};
862 : : PgStatShared_HashEntry *shent;
863 : 46452 : bool freed = true;
864 : :
865 : : /* delete local reference */
866 [ + + ]: 46452 : if (pgStatEntryRefHash)
867 : : {
868 : : PgStat_EntryRefHashEntry *lohashent =
331 tgl@sss.pgh.pa.us 869 : 37782 : pgstat_entry_ref_hash_lookup(pgStatEntryRefHash, key);
870 : :
739 andres@anarazel.de 871 [ + + ]: 37782 : if (lohashent)
872 : 31044 : pgstat_release_entry_ref(lohashent->key, lohashent->entry_ref,
873 : : true);
874 : : }
875 : :
876 : : /* mark entry in shared hashtable as deleted, drop if possible */
877 : 46452 : shent = dshash_find(pgStatLocal.shared_hash, &key, true);
878 [ + + ]: 46452 : if (shent)
879 : : {
880 : 31157 : freed = pgstat_drop_entry_internal(shent, NULL);
881 : :
882 : : /*
883 : : * Database stats contain other stats. Drop those as well when
884 : : * dropping the database. XXX: Perhaps this should be done in a
885 : : * slightly more principled way? But not obvious what that'd look
886 : : * like, and so far this is the only case...
887 : : */
888 [ + + ]: 31157 : if (key.kind == PGSTAT_KIND_DATABASE)
889 : 22 : pgstat_drop_database_and_contents(key.dboid);
890 : : }
891 : :
892 : 46452 : return freed;
893 : : }
894 : :
895 : : void
896 : 243 : pgstat_drop_all_entries(void)
897 : : {
898 : : dshash_seq_status hstat;
899 : : PgStatShared_HashEntry *ps;
900 : 243 : uint64 not_freed_count = 0;
901 : :
729 902 : 243 : dshash_seq_init(&hstat, pgStatLocal.shared_hash, true);
739 903 [ + + ]: 299 : while ((ps = dshash_seq_next(&hstat)) != NULL)
904 : : {
905 [ - + ]: 56 : if (ps->dropped)
739 andres@anarazel.de 906 :UBC 0 : continue;
907 : :
739 andres@anarazel.de 908 [ - + ]:CBC 56 : if (!pgstat_drop_entry_internal(ps, &hstat))
739 andres@anarazel.de 909 :UBC 0 : not_freed_count++;
910 : : }
739 andres@anarazel.de 911 :CBC 243 : dshash_seq_term(&hstat);
912 : :
913 [ - + ]: 243 : if (not_freed_count > 0)
739 andres@anarazel.de 914 :UBC 0 : pgstat_request_entry_refs_gc();
739 andres@anarazel.de 915 :CBC 243 : }
916 : :
917 : : static void
918 : 8647 : shared_stat_reset_contents(PgStat_Kind kind, PgStatShared_Common *header,
919 : : TimestampTz ts)
920 : : {
921 : 8647 : const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind);
922 : :
923 : 8647 : memset(pgstat_get_entry_data(kind, header), 0,
924 : : pgstat_get_entry_len(kind));
925 : :
926 [ + + ]: 8647 : if (kind_info->reset_timestamp_cb)
927 : 171 : kind_info->reset_timestamp_cb(header, ts);
928 : 8647 : }
929 : :
930 : : /*
931 : : * Reset one variable-numbered stats entry.
932 : : */
933 : : void
934 : 157 : pgstat_reset_entry(PgStat_Kind kind, Oid dboid, Oid objoid, TimestampTz ts)
935 : : {
936 : : PgStat_EntryRef *entry_ref;
937 : :
938 [ - + ]: 157 : Assert(!pgstat_get_kind_info(kind)->fixed_amount);
939 : :
940 : 157 : entry_ref = pgstat_get_entry_ref(kind, dboid, objoid, false, NULL);
941 [ + + - + ]: 157 : if (!entry_ref || entry_ref->shared_entry->dropped)
942 : 1 : return;
943 : :
734 tgl@sss.pgh.pa.us 944 : 156 : (void) pgstat_lock_entry(entry_ref, false);
739 andres@anarazel.de 945 : 156 : shared_stat_reset_contents(kind, entry_ref->shared_stats, ts);
946 : 156 : pgstat_unlock_entry(entry_ref);
947 : : }
948 : :
949 : : /*
950 : : * Scan through the shared hashtable of stats, resetting statistics if
951 : : * approved by the provided do_reset() function.
952 : : */
953 : : void
954 : 17 : pgstat_reset_matching_entries(bool (*do_reset) (PgStatShared_HashEntry *, Datum),
955 : : Datum match_data, TimestampTz ts)
956 : : {
957 : : dshash_seq_status hstat;
958 : : PgStatShared_HashEntry *p;
959 : :
960 : : /* dshash entry is not modified, take shared lock */
961 : 17 : dshash_seq_init(&hstat, pgStatLocal.shared_hash, false);
962 [ + + ]: 12589 : while ((p = dshash_seq_next(&hstat)) != NULL)
963 : : {
964 : : PgStatShared_Common *header;
965 : :
966 [ + + ]: 12572 : if (p->dropped)
967 : 1 : continue;
968 : :
969 [ + + ]: 12571 : if (!do_reset(p, match_data))
970 : 4080 : continue;
971 : :
972 : 8491 : header = dsa_get_address(pgStatLocal.dsa, p->body);
973 : :
974 : 8491 : LWLockAcquire(&header->lock, LW_EXCLUSIVE);
975 : :
976 : 8491 : shared_stat_reset_contents(p->key.kind, header, ts);
977 : :
978 : 8491 : LWLockRelease(&header->lock);
979 : : }
980 : 17 : dshash_seq_term(&hstat);
981 : 17 : }
982 : :
983 : : static bool
984 : 1452 : match_kind(PgStatShared_HashEntry *p, Datum match_data)
985 : : {
986 : 1452 : return p->key.kind == DatumGetInt32(match_data);
987 : : }
988 : :
989 : : void
990 : 4 : pgstat_reset_entries_of_kind(PgStat_Kind kind, TimestampTz ts)
991 : : {
992 : 4 : pgstat_reset_matching_entries(match_kind, Int32GetDatum(kind), ts);
993 : 4 : }
994 : :
995 : : static void
996 : 2037741 : pgstat_setup_memcxt(void)
997 : : {
998 [ + + ]: 2037741 : if (unlikely(!pgStatSharedRefContext))
999 : 15760 : pgStatSharedRefContext =
576 1000 : 15760 : AllocSetContextCreate(TopMemoryContext,
1001 : : "PgStat Shared Ref",
1002 : : ALLOCSET_SMALL_SIZES);
739 1003 [ + + ]: 2037741 : if (unlikely(!pgStatEntryRefHashContext))
1004 : 15760 : pgStatEntryRefHashContext =
576 1005 : 15760 : AllocSetContextCreate(TopMemoryContext,
1006 : : "PgStat Shared Ref Hash",
1007 : : ALLOCSET_SMALL_SIZES);
739 1008 : 2037741 : }
|