Age Owner Branch data TLA Line data Source code
1 : : /* ----------
2 : : * wait_event.c
3 : : * Wait event reporting infrastructure.
4 : : *
5 : : * Copyright (c) 2001-2024, PostgreSQL Global Development Group
6 : : *
7 : : *
8 : : * IDENTIFICATION
9 : : * src/backend/utils/activity/wait_event.c
10 : : *
11 : : * NOTES
12 : : *
13 : : * To make pgstat_report_wait_start() and pgstat_report_wait_end() as
14 : : * lightweight as possible, they do not check if shared memory (MyProc
15 : : * specifically, where the wait event is stored) is already available. Instead
16 : : * we initially set my_wait_event_info to a process local variable, which then
17 : : * is redirected to shared memory using pgstat_set_wait_event_storage(). For
18 : : * the same reason pgstat_track_activities is not checked - the check adds
19 : : * more work than it saves.
20 : : *
21 : : * ----------
22 : : */
23 : : #include "postgres.h"
24 : :
25 : : #include "port/pg_bitutils.h"
26 : : #include "storage/lmgr.h" /* for GetLockNameFromTagType */
27 : : #include "storage/lwlock.h" /* for GetLWLockIdentifier */
28 : : #include "storage/spin.h"
29 : : #include "utils/wait_event.h"
30 : :
31 : :
32 : : static const char *pgstat_get_wait_activity(WaitEventActivity w);
33 : : static const char *pgstat_get_wait_bufferpin(WaitEventBufferPin w);
34 : : static const char *pgstat_get_wait_client(WaitEventClient w);
35 : : static const char *pgstat_get_wait_ipc(WaitEventIPC w);
36 : : static const char *pgstat_get_wait_timeout(WaitEventTimeout w);
37 : : static const char *pgstat_get_wait_io(WaitEventIO w);
38 : :
39 : :
40 : : static uint32 local_my_wait_event_info;
41 : : uint32 *my_wait_event_info = &local_my_wait_event_info;
42 : :
43 : : #define WAIT_EVENT_CLASS_MASK 0xFF000000
44 : : #define WAIT_EVENT_ID_MASK 0x0000FFFF
45 : :
46 : : /*
47 : : * Hash tables for storing custom wait event ids and their names in
48 : : * shared memory.
49 : : *
50 : : * WaitEventExtensionHashById is used to find the name from an event id.
51 : : * Any backend can search it to find custom wait events.
52 : : *
53 : : * WaitEventExtensionHashByName is used to find the event ID from a name.
54 : : * It is used to ensure that no duplicated entries are registered.
55 : : *
56 : : * The size of the hash table is based on the assumption that
57 : : * WAIT_EVENT_EXTENSION_HASH_INIT_SIZE is enough for most cases, and it seems
58 : : * unlikely that the number of entries will reach
59 : : * WAIT_EVENT_EXTENSION_HASH_MAX_SIZE.
60 : : */
61 : : static HTAB *WaitEventExtensionHashById; /* find names from IDs */
62 : : static HTAB *WaitEventExtensionHashByName; /* find IDs from names */
63 : :
64 : : #define WAIT_EVENT_EXTENSION_HASH_INIT_SIZE 16
65 : : #define WAIT_EVENT_EXTENSION_HASH_MAX_SIZE 128
66 : :
67 : : /* hash table entries */
68 : : typedef struct WaitEventExtensionEntryById
69 : : {
70 : : uint16 event_id; /* hash key */
71 : : char wait_event_name[NAMEDATALEN]; /* custom wait event name */
72 : : } WaitEventExtensionEntryById;
73 : :
74 : : typedef struct WaitEventExtensionEntryByName
75 : : {
76 : : char wait_event_name[NAMEDATALEN]; /* hash key */
77 : : uint16 event_id; /* wait event ID */
78 : : } WaitEventExtensionEntryByName;
79 : :
80 : :
81 : : /* dynamic allocation counter for custom wait events in extensions */
82 : : typedef struct WaitEventExtensionCounterData
83 : : {
84 : : int nextId; /* next ID to assign */
85 : : slock_t mutex; /* protects the counter */
86 : : } WaitEventExtensionCounterData;
87 : :
88 : : /* pointer to the shared memory */
89 : : static WaitEventExtensionCounterData *WaitEventExtensionCounter;
90 : :
91 : : /* first event ID of custom wait events for extensions */
92 : : #define NUM_BUILTIN_WAIT_EVENT_EXTENSION \
93 : : (WAIT_EVENT_EXTENSION_FIRST_USER_DEFINED - WAIT_EVENT_EXTENSION)
94 : :
95 : : /* wait event info for extensions */
96 : : #define WAIT_EVENT_EXTENSION_INFO(eventId) (PG_WAIT_EXTENSION | eventId)
97 : :
98 : : static const char *GetWaitEventExtensionIdentifier(uint16 eventId);
99 : :
100 : : /*
101 : : * Return the space for dynamic shared hash tables and dynamic allocation counter.
102 : : */
103 : : Size
258 michael@paquier.xyz 104 :GNC 1679 : WaitEventExtensionShmemSize(void)
105 : : {
106 : : Size sz;
107 : :
244 108 : 1679 : sz = MAXALIGN(sizeof(WaitEventExtensionCounterData));
109 : 1679 : sz = add_size(sz, hash_estimate_size(WAIT_EVENT_EXTENSION_HASH_MAX_SIZE,
110 : : sizeof(WaitEventExtensionEntryById)));
111 : 1679 : sz = add_size(sz, hash_estimate_size(WAIT_EVENT_EXTENSION_HASH_MAX_SIZE,
112 : : sizeof(WaitEventExtensionEntryByName)));
113 : 1679 : return sz;
114 : : }
115 : :
116 : : /*
117 : : * Allocate shmem space for dynamic shared hash and dynamic allocation counter.
118 : : */
119 : : void
258 120 : 898 : WaitEventExtensionShmemInit(void)
121 : : {
122 : : bool found;
123 : : HASHCTL info;
124 : :
125 : 898 : WaitEventExtensionCounter = (WaitEventExtensionCounterData *)
126 : 898 : ShmemInitStruct("WaitEventExtensionCounterData",
127 : : sizeof(WaitEventExtensionCounterData), &found);
128 : :
129 [ + - ]: 898 : if (!found)
130 : : {
131 : : /* initialize the allocation counter and its spinlock. */
132 : 898 : WaitEventExtensionCounter->nextId = NUM_BUILTIN_WAIT_EVENT_EXTENSION;
133 : 898 : SpinLockInit(&WaitEventExtensionCounter->mutex);
134 : : }
135 : :
136 : : /* initialize or attach the hash tables to store custom wait events */
244 137 : 898 : info.keysize = sizeof(uint16);
138 : 898 : info.entrysize = sizeof(WaitEventExtensionEntryById);
139 : 898 : WaitEventExtensionHashById = ShmemInitHash("WaitEventExtension hash by id",
140 : : WAIT_EVENT_EXTENSION_HASH_INIT_SIZE,
141 : : WAIT_EVENT_EXTENSION_HASH_MAX_SIZE,
142 : : &info,
143 : : HASH_ELEM | HASH_BLOBS);
144 : :
145 : : /* key is a NULL-terminated string */
146 : 898 : info.keysize = sizeof(char[NAMEDATALEN]);
147 : 898 : info.entrysize = sizeof(WaitEventExtensionEntryByName);
148 : 898 : WaitEventExtensionHashByName = ShmemInitHash("WaitEventExtension hash by name",
149 : : WAIT_EVENT_EXTENSION_HASH_INIT_SIZE,
150 : : WAIT_EVENT_EXTENSION_HASH_MAX_SIZE,
151 : : &info,
152 : : HASH_ELEM | HASH_STRINGS);
258 153 : 898 : }
154 : :
155 : : /*
156 : : * Allocate a new event ID and return the wait event info.
157 : : *
158 : : * If the wait event name is already defined, this does not allocate a new
159 : : * entry; it returns the wait event information associated to the name.
160 : : */
161 : : uint32
244 162 : 46 : WaitEventExtensionNew(const char *wait_event_name)
163 : : {
164 : : uint16 eventId;
165 : : bool found;
166 : : WaitEventExtensionEntryByName *entry_by_name;
167 : : WaitEventExtensionEntryById *entry_by_id;
168 : :
169 : : /* Check the limit of the length of the event name */
170 [ - + ]: 46 : if (strlen(wait_event_name) >= NAMEDATALEN)
244 michael@paquier.xyz 171 [ # # ]:UNC 0 : elog(ERROR,
172 : : "cannot use custom wait event string longer than %u characters",
173 : : NAMEDATALEN - 1);
174 : :
175 : : /*
176 : : * Check if the wait event info associated to the name is already defined,
177 : : * and return it if so.
178 : : */
244 michael@paquier.xyz 179 :GNC 46 : LWLockAcquire(WaitEventExtensionLock, LW_SHARED);
180 : : entry_by_name = (WaitEventExtensionEntryByName *)
181 : 46 : hash_search(WaitEventExtensionHashByName, wait_event_name,
182 : : HASH_FIND, &found);
183 : 46 : LWLockRelease(WaitEventExtensionLock);
184 [ + + ]: 46 : if (found)
185 : 22 : return WAIT_EVENT_EXTENSION_INFO(entry_by_name->event_id);
186 : :
187 : : /*
188 : : * Allocate and register a new wait event. Recheck if the event name
189 : : * exists, as it could be possible that a concurrent process has inserted
190 : : * one with the same name since the LWLock acquired again here was
191 : : * previously released.
192 : : */
193 : 24 : LWLockAcquire(WaitEventExtensionLock, LW_EXCLUSIVE);
194 : : entry_by_name = (WaitEventExtensionEntryByName *)
195 : 24 : hash_search(WaitEventExtensionHashByName, wait_event_name,
196 : : HASH_FIND, &found);
197 [ - + ]: 24 : if (found)
198 : : {
244 michael@paquier.xyz 199 :UNC 0 : LWLockRelease(WaitEventExtensionLock);
200 : 0 : return WAIT_EVENT_EXTENSION_INFO(entry_by_name->event_id);
201 : : }
202 : :
203 : : /* Allocate a new event Id */
258 michael@paquier.xyz 204 [ - + ]:GNC 24 : SpinLockAcquire(&WaitEventExtensionCounter->mutex);
205 : :
244 206 [ - + ]: 24 : if (WaitEventExtensionCounter->nextId >= WAIT_EVENT_EXTENSION_HASH_MAX_SIZE)
207 : : {
258 michael@paquier.xyz 208 :UNC 0 : SpinLockRelease(&WaitEventExtensionCounter->mutex);
209 [ # # ]: 0 : ereport(ERROR,
210 : : errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
211 : : errmsg("too many wait events for extensions"));
212 : : }
213 : :
258 michael@paquier.xyz 214 :GNC 24 : eventId = WaitEventExtensionCounter->nextId++;
215 : :
216 : 24 : SpinLockRelease(&WaitEventExtensionCounter->mutex);
217 : :
218 : : /* Register the new wait event */
219 : : entry_by_id = (WaitEventExtensionEntryById *)
244 220 : 24 : hash_search(WaitEventExtensionHashById, &eventId,
221 : : HASH_ENTER, &found);
222 [ - + ]: 24 : Assert(!found);
223 : 24 : strlcpy(entry_by_id->wait_event_name, wait_event_name,
224 : : sizeof(entry_by_id->wait_event_name));
225 : :
226 : : entry_by_name = (WaitEventExtensionEntryByName *)
227 : 24 : hash_search(WaitEventExtensionHashByName, wait_event_name,
228 : : HASH_ENTER, &found);
229 [ - + ]: 24 : Assert(!found);
230 : 24 : entry_by_name->event_id = eventId;
231 : :
232 : 24 : LWLockRelease(WaitEventExtensionLock);
233 : :
234 : 24 : return WAIT_EVENT_EXTENSION_INFO(eventId);
235 : : }
236 : :
237 : : /*
238 : : * Return the name of an wait event ID for extension.
239 : : */
240 : : static const char *
258 241 : 34 : GetWaitEventExtensionIdentifier(uint16 eventId)
242 : : {
243 : : bool found;
244 : : WaitEventExtensionEntryById *entry;
245 : :
246 : : /* Built-in event? */
247 [ - + ]: 34 : if (eventId < NUM_BUILTIN_WAIT_EVENT_EXTENSION)
258 michael@paquier.xyz 248 :UNC 0 : return "Extension";
249 : :
250 : : /* It is a user-defined wait event, so lookup hash table. */
244 michael@paquier.xyz 251 :GNC 34 : LWLockAcquire(WaitEventExtensionLock, LW_SHARED);
252 : : entry = (WaitEventExtensionEntryById *)
253 : 34 : hash_search(WaitEventExtensionHashById, &eventId,
254 : : HASH_FIND, &found);
255 : 34 : LWLockRelease(WaitEventExtensionLock);
256 : :
257 [ - + ]: 34 : if (!entry)
244 michael@paquier.xyz 258 [ # # ]:UNC 0 : elog(ERROR, "could not find custom wait event name for ID %u",
259 : : eventId);
260 : :
244 michael@paquier.xyz 261 :GNC 34 : return entry->wait_event_name;
262 : : }
263 : :
264 : :
265 : : /*
266 : : * Returns a list of currently defined custom wait event names for extensions.
267 : : * The result is a palloc'd array, with the number of elements saved in
268 : : * *nwaitevents.
269 : : */
270 : : char **
238 271 : 4 : GetWaitEventExtensionNames(int *nwaitevents)
272 : : {
273 : : char **waiteventnames;
274 : : WaitEventExtensionEntryByName *hentry;
275 : : HASH_SEQ_STATUS hash_seq;
276 : : int index;
277 : : int els;
278 : :
279 : 4 : LWLockAcquire(WaitEventExtensionLock, LW_SHARED);
280 : :
281 : : /* Now we can safely count the number of entries */
282 : 4 : els = hash_get_num_entries(WaitEventExtensionHashByName);
283 : :
284 : : /* Allocate enough space for all entries */
285 : 4 : waiteventnames = palloc(els * sizeof(char *));
286 : :
287 : : /* Now scan the hash table to copy the data */
288 : 4 : hash_seq_init(&hash_seq, WaitEventExtensionHashByName);
289 : :
290 : 4 : index = 0;
291 [ + + ]: 5 : while ((hentry = (WaitEventExtensionEntryByName *) hash_seq_search(&hash_seq)) != NULL)
292 : : {
293 : 1 : waiteventnames[index] = pstrdup(hentry->wait_event_name);
294 : 1 : index++;
295 : : }
296 : :
297 : 4 : LWLockRelease(WaitEventExtensionLock);
298 : :
299 [ - + ]: 4 : Assert(index == els);
300 : :
301 : 4 : *nwaitevents = index;
302 : 4 : return waiteventnames;
303 : : }
304 : :
305 : : /*
306 : : * Configure wait event reporting to report wait events to *wait_event_info.
307 : : * *wait_event_info needs to be valid until pgstat_reset_wait_event_storage()
308 : : * is called.
309 : : *
310 : : * Expected to be called during backend startup, to point my_wait_event_info
311 : : * into shared memory.
312 : : */
313 : : void
1107 andres@anarazel.de 314 :CBC 19729 : pgstat_set_wait_event_storage(uint32 *wait_event_info)
315 : : {
316 : 19729 : my_wait_event_info = wait_event_info;
317 : 19729 : }
318 : :
319 : : /*
320 : : * Reset wait event storage location.
321 : : *
322 : : * Expected to be called during backend shutdown, before the location set up
323 : : * pgstat_set_wait_event_storage() becomes invalid.
324 : : */
325 : : void
326 : 18199 : pgstat_reset_wait_event_storage(void)
327 : : {
328 : 18199 : my_wait_event_info = &local_my_wait_event_info;
329 : 18199 : }
330 : :
331 : : /* ----------
332 : : * pgstat_get_wait_event_type() -
333 : : *
334 : : * Return a string representing the current wait event type, backend is
335 : : * waiting on.
336 : : */
337 : : const char *
1108 338 : 6088 : pgstat_get_wait_event_type(uint32 wait_event_info)
339 : : {
340 : : uint32 classId;
341 : : const char *event_type;
342 : :
343 : : /* report process as not waiting. */
344 [ + + ]: 6088 : if (wait_event_info == 0)
345 : 1351 : return NULL;
346 : :
258 michael@paquier.xyz 347 :GNC 4737 : classId = wait_event_info & WAIT_EVENT_CLASS_MASK;
348 : :
1108 andres@anarazel.de 349 [ - + - + :CBC 4737 : switch (classId)
+ + + + +
- ]
350 : : {
1108 andres@anarazel.de 351 :UBC 0 : case PG_WAIT_LWLOCK:
352 : 0 : event_type = "LWLock";
353 : 0 : break;
1108 andres@anarazel.de 354 :CBC 7 : case PG_WAIT_LOCK:
355 : 7 : event_type = "Lock";
356 : 7 : break;
286 michael@paquier.xyz 357 :UNC 0 : case PG_WAIT_BUFFERPIN:
1108 andres@anarazel.de 358 :UBC 0 : event_type = "BufferPin";
359 : 0 : break;
1108 andres@anarazel.de 360 :CBC 4053 : case PG_WAIT_ACTIVITY:
361 : 4053 : event_type = "Activity";
362 : 4053 : break;
363 : 619 : case PG_WAIT_CLIENT:
364 : 619 : event_type = "Client";
365 : 619 : break;
366 : 34 : case PG_WAIT_EXTENSION:
367 : 34 : event_type = "Extension";
368 : 34 : break;
1108 andres@anarazel.de 369 :GBC 2 : case PG_WAIT_IPC:
370 : 2 : event_type = "IPC";
371 : 2 : break;
1108 andres@anarazel.de 372 :CBC 18 : case PG_WAIT_TIMEOUT:
373 : 18 : event_type = "Timeout";
374 : 18 : break;
375 : 4 : case PG_WAIT_IO:
376 : 4 : event_type = "IO";
377 : 4 : break;
1108 andres@anarazel.de 378 :UBC 0 : default:
379 : 0 : event_type = "???";
380 : 0 : break;
381 : : }
382 : :
1108 andres@anarazel.de 383 :CBC 4737 : return event_type;
384 : : }
385 : :
386 : : /* ----------
387 : : * pgstat_get_wait_event() -
388 : : *
389 : : * Return a string representing the current wait event, backend is
390 : : * waiting on.
391 : : */
392 : : const char *
393 : 6088 : pgstat_get_wait_event(uint32 wait_event_info)
394 : : {
395 : : uint32 classId;
396 : : uint16 eventId;
397 : : const char *event_name;
398 : :
399 : : /* report process as not waiting. */
400 [ + + ]: 6088 : if (wait_event_info == 0)
401 : 1351 : return NULL;
402 : :
258 michael@paquier.xyz 403 :GNC 4737 : classId = wait_event_info & WAIT_EVENT_CLASS_MASK;
404 : 4737 : eventId = wait_event_info & WAIT_EVENT_ID_MASK;
405 : :
1108 andres@anarazel.de 406 [ - + + - :CBC 4737 : switch (classId)
+ + + + +
- ]
407 : : {
1108 andres@anarazel.de 408 :UBC 0 : case PG_WAIT_LWLOCK:
409 : 0 : event_name = GetLWLockIdentifier(classId, eventId);
410 : 0 : break;
1108 andres@anarazel.de 411 :CBC 7 : case PG_WAIT_LOCK:
412 : 7 : event_name = GetLockNameFromTagType(eventId);
413 : 7 : break;
258 michael@paquier.xyz 414 :GNC 34 : case PG_WAIT_EXTENSION:
415 : 34 : event_name = GetWaitEventExtensionIdentifier(eventId);
258 michael@paquier.xyz 416 :GBC 34 : break;
286 michael@paquier.xyz 417 :UNC 0 : case PG_WAIT_BUFFERPIN:
418 : : {
419 : 0 : WaitEventBufferPin w = (WaitEventBufferPin) wait_event_info;
420 : :
421 : 0 : event_name = pgstat_get_wait_bufferpin(w);
422 : 0 : break;
423 : : }
1108 andres@anarazel.de 424 :CBC 4053 : case PG_WAIT_ACTIVITY:
425 : : {
426 : 4053 : WaitEventActivity w = (WaitEventActivity) wait_event_info;
427 : :
428 : 4053 : event_name = pgstat_get_wait_activity(w);
429 : 4053 : break;
430 : : }
431 : 619 : case PG_WAIT_CLIENT:
432 : : {
433 : 619 : WaitEventClient w = (WaitEventClient) wait_event_info;
434 : :
435 : 619 : event_name = pgstat_get_wait_client(w);
436 : 619 : break;
437 : : }
1108 andres@anarazel.de 438 :GBC 2 : case PG_WAIT_IPC:
439 : : {
440 : 2 : WaitEventIPC w = (WaitEventIPC) wait_event_info;
441 : :
442 : 2 : event_name = pgstat_get_wait_ipc(w);
443 : 2 : break;
444 : : }
1108 andres@anarazel.de 445 :CBC 18 : case PG_WAIT_TIMEOUT:
446 : : {
447 : 18 : WaitEventTimeout w = (WaitEventTimeout) wait_event_info;
448 : :
449 : 18 : event_name = pgstat_get_wait_timeout(w);
450 : 18 : break;
451 : : }
452 : 4 : case PG_WAIT_IO:
453 : : {
454 : 4 : WaitEventIO w = (WaitEventIO) wait_event_info;
455 : :
456 : 4 : event_name = pgstat_get_wait_io(w);
457 : 4 : break;
458 : : }
1108 andres@anarazel.de 459 :UBC 0 : default:
460 : 0 : event_name = "unknown wait event";
461 : 0 : break;
462 : : }
463 : :
1108 andres@anarazel.de 464 :CBC 4737 : return event_name;
465 : : }
466 : :
467 : : #include "pgstat_wait_event.c"
|