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