xref: /freebsd/contrib/jemalloc/src/prof_sys.c (revision c43cad87172039ccf38172129c79755ea79e6102)
1 #define JEMALLOC_PROF_SYS_C_
2 #include "jemalloc/internal/jemalloc_preamble.h"
3 #include "jemalloc/internal/jemalloc_internal_includes.h"
4 
5 #include "jemalloc/internal/buf_writer.h"
6 #include "jemalloc/internal/ctl.h"
7 #include "jemalloc/internal/prof_data.h"
8 #include "jemalloc/internal/prof_sys.h"
9 
10 #ifdef JEMALLOC_PROF_LIBUNWIND
11 #define UNW_LOCAL_ONLY
12 #include <libunwind.h>
13 #endif
14 
15 #ifdef JEMALLOC_PROF_LIBGCC
16 /*
17  * We have a circular dependency -- jemalloc_internal.h tells us if we should
18  * use libgcc's unwinding functionality, but after we've included that, we've
19  * already hooked _Unwind_Backtrace.  We'll temporarily disable hooking.
20  */
21 #undef _Unwind_Backtrace
22 #include <unwind.h>
23 #define _Unwind_Backtrace JEMALLOC_TEST_HOOK(_Unwind_Backtrace, test_hooks_libc_hook)
24 #endif
25 
26 /******************************************************************************/
27 
28 malloc_mutex_t prof_dump_filename_mtx;
29 
30 bool prof_do_mock = false;
31 
32 static uint64_t prof_dump_seq;
33 static uint64_t prof_dump_iseq;
34 static uint64_t prof_dump_mseq;
35 static uint64_t prof_dump_useq;
36 
37 static char *prof_prefix = NULL;
38 
39 /* The fallback allocator profiling functionality will use. */
40 base_t *prof_base;
41 
42 void
43 bt_init(prof_bt_t *bt, void **vec) {
44 	cassert(config_prof);
45 
46 	bt->vec = vec;
47 	bt->len = 0;
48 }
49 
50 #ifdef JEMALLOC_PROF_LIBUNWIND
51 static void
52 prof_backtrace_impl(void **vec, unsigned *len, unsigned max_len) {
53 	int nframes;
54 
55 	cassert(config_prof);
56 	assert(*len == 0);
57 	assert(vec != NULL);
58 	assert(max_len == PROF_BT_MAX);
59 
60 	nframes = unw_backtrace(vec, PROF_BT_MAX);
61 	if (nframes <= 0) {
62 		return;
63 	}
64 	*len = nframes;
65 }
66 #elif (defined(JEMALLOC_PROF_LIBGCC))
67 static _Unwind_Reason_Code
68 prof_unwind_init_callback(struct _Unwind_Context *context, void *arg) {
69 	cassert(config_prof);
70 
71 	return _URC_NO_REASON;
72 }
73 
74 static _Unwind_Reason_Code
75 prof_unwind_callback(struct _Unwind_Context *context, void *arg) {
76 	prof_unwind_data_t *data = (prof_unwind_data_t *)arg;
77 	void *ip;
78 
79 	cassert(config_prof);
80 
81 	ip = (void *)_Unwind_GetIP(context);
82 	if (ip == NULL) {
83 		return _URC_END_OF_STACK;
84 	}
85 	data->vec[*data->len] = ip;
86 	(*data->len)++;
87 	if (*data->len == data->max) {
88 		return _URC_END_OF_STACK;
89 	}
90 
91 	return _URC_NO_REASON;
92 }
93 
94 static void
95 prof_backtrace_impl(void **vec, unsigned *len, unsigned max_len) {
96 	prof_unwind_data_t data = {vec, len, max_len};
97 
98 	cassert(config_prof);
99 	assert(vec != NULL);
100 	assert(max_len == PROF_BT_MAX);
101 
102 	_Unwind_Backtrace(prof_unwind_callback, &data);
103 }
104 #elif (defined(JEMALLOC_PROF_GCC))
105 static void
106 prof_backtrace_impl(void **vec, unsigned *len, unsigned max_len) {
107 #define BT_FRAME(i)							\
108 	if ((i) < max_len) {						\
109 		void *p;						\
110 		if (__builtin_frame_address(i) == 0) {			\
111 			return;						\
112 		}							\
113 		p = __builtin_return_address(i);			\
114 		if (p == NULL) {					\
115 			return;						\
116 		}							\
117 		vec[(i)] = p;						\
118 		*len = (i) + 1;						\
119 	} else {							\
120 		return;							\
121 	}
122 
123 	cassert(config_prof);
124 	assert(vec != NULL);
125 	assert(max_len == PROF_BT_MAX);
126 
127 	BT_FRAME(0)
128 	BT_FRAME(1)
129 	BT_FRAME(2)
130 	BT_FRAME(3)
131 	BT_FRAME(4)
132 	BT_FRAME(5)
133 	BT_FRAME(6)
134 	BT_FRAME(7)
135 	BT_FRAME(8)
136 	BT_FRAME(9)
137 
138 	BT_FRAME(10)
139 	BT_FRAME(11)
140 	BT_FRAME(12)
141 	BT_FRAME(13)
142 	BT_FRAME(14)
143 	BT_FRAME(15)
144 	BT_FRAME(16)
145 	BT_FRAME(17)
146 	BT_FRAME(18)
147 	BT_FRAME(19)
148 
149 	BT_FRAME(20)
150 	BT_FRAME(21)
151 	BT_FRAME(22)
152 	BT_FRAME(23)
153 	BT_FRAME(24)
154 	BT_FRAME(25)
155 	BT_FRAME(26)
156 	BT_FRAME(27)
157 	BT_FRAME(28)
158 	BT_FRAME(29)
159 
160 	BT_FRAME(30)
161 	BT_FRAME(31)
162 	BT_FRAME(32)
163 	BT_FRAME(33)
164 	BT_FRAME(34)
165 	BT_FRAME(35)
166 	BT_FRAME(36)
167 	BT_FRAME(37)
168 	BT_FRAME(38)
169 	BT_FRAME(39)
170 
171 	BT_FRAME(40)
172 	BT_FRAME(41)
173 	BT_FRAME(42)
174 	BT_FRAME(43)
175 	BT_FRAME(44)
176 	BT_FRAME(45)
177 	BT_FRAME(46)
178 	BT_FRAME(47)
179 	BT_FRAME(48)
180 	BT_FRAME(49)
181 
182 	BT_FRAME(50)
183 	BT_FRAME(51)
184 	BT_FRAME(52)
185 	BT_FRAME(53)
186 	BT_FRAME(54)
187 	BT_FRAME(55)
188 	BT_FRAME(56)
189 	BT_FRAME(57)
190 	BT_FRAME(58)
191 	BT_FRAME(59)
192 
193 	BT_FRAME(60)
194 	BT_FRAME(61)
195 	BT_FRAME(62)
196 	BT_FRAME(63)
197 	BT_FRAME(64)
198 	BT_FRAME(65)
199 	BT_FRAME(66)
200 	BT_FRAME(67)
201 	BT_FRAME(68)
202 	BT_FRAME(69)
203 
204 	BT_FRAME(70)
205 	BT_FRAME(71)
206 	BT_FRAME(72)
207 	BT_FRAME(73)
208 	BT_FRAME(74)
209 	BT_FRAME(75)
210 	BT_FRAME(76)
211 	BT_FRAME(77)
212 	BT_FRAME(78)
213 	BT_FRAME(79)
214 
215 	BT_FRAME(80)
216 	BT_FRAME(81)
217 	BT_FRAME(82)
218 	BT_FRAME(83)
219 	BT_FRAME(84)
220 	BT_FRAME(85)
221 	BT_FRAME(86)
222 	BT_FRAME(87)
223 	BT_FRAME(88)
224 	BT_FRAME(89)
225 
226 	BT_FRAME(90)
227 	BT_FRAME(91)
228 	BT_FRAME(92)
229 	BT_FRAME(93)
230 	BT_FRAME(94)
231 	BT_FRAME(95)
232 	BT_FRAME(96)
233 	BT_FRAME(97)
234 	BT_FRAME(98)
235 	BT_FRAME(99)
236 
237 	BT_FRAME(100)
238 	BT_FRAME(101)
239 	BT_FRAME(102)
240 	BT_FRAME(103)
241 	BT_FRAME(104)
242 	BT_FRAME(105)
243 	BT_FRAME(106)
244 	BT_FRAME(107)
245 	BT_FRAME(108)
246 	BT_FRAME(109)
247 
248 	BT_FRAME(110)
249 	BT_FRAME(111)
250 	BT_FRAME(112)
251 	BT_FRAME(113)
252 	BT_FRAME(114)
253 	BT_FRAME(115)
254 	BT_FRAME(116)
255 	BT_FRAME(117)
256 	BT_FRAME(118)
257 	BT_FRAME(119)
258 
259 	BT_FRAME(120)
260 	BT_FRAME(121)
261 	BT_FRAME(122)
262 	BT_FRAME(123)
263 	BT_FRAME(124)
264 	BT_FRAME(125)
265 	BT_FRAME(126)
266 	BT_FRAME(127)
267 #undef BT_FRAME
268 }
269 #else
270 static void
271 prof_backtrace_impl(void **vec, unsigned *len, unsigned max_len) {
272 	cassert(config_prof);
273 	not_reached();
274 }
275 #endif
276 
277 void
278 prof_backtrace(tsd_t *tsd, prof_bt_t *bt) {
279 	cassert(config_prof);
280 	prof_backtrace_hook_t prof_backtrace_hook = prof_backtrace_hook_get();
281 	assert(prof_backtrace_hook != NULL);
282 
283 	pre_reentrancy(tsd, NULL);
284 	prof_backtrace_hook(bt->vec, &bt->len, PROF_BT_MAX);
285 	post_reentrancy(tsd);
286 }
287 
288 void
289 prof_hooks_init() {
290 	prof_backtrace_hook_set(&prof_backtrace_impl);
291 	prof_dump_hook_set(NULL);
292 }
293 
294 void
295 prof_unwind_init() {
296 #ifdef JEMALLOC_PROF_LIBGCC
297 	/*
298 	 * Cause the backtracing machinery to allocate its internal
299 	 * state before enabling profiling.
300 	 */
301 	_Unwind_Backtrace(prof_unwind_init_callback, NULL);
302 #endif
303 }
304 
305 static int
306 prof_sys_thread_name_read_impl(char *buf, size_t limit) {
307 #if defined(JEMALLOC_HAVE_PTHREAD_GETNAME_NP)
308 	return pthread_getname_np(pthread_self(), buf, limit);
309 #elif defined(JEMALLOC_HAVE_PTHREAD_GET_NAME_NP)
310 	pthread_get_name_np(pthread_self(), buf, limit);
311 	return 0;
312 #else
313 	return ENOSYS;
314 #endif
315 }
316 prof_sys_thread_name_read_t *JET_MUTABLE prof_sys_thread_name_read =
317     prof_sys_thread_name_read_impl;
318 
319 void
320 prof_sys_thread_name_fetch(tsd_t *tsd) {
321 #define THREAD_NAME_MAX_LEN 16
322 	char buf[THREAD_NAME_MAX_LEN];
323 	if (!prof_sys_thread_name_read(buf, THREAD_NAME_MAX_LEN)) {
324 		prof_thread_name_set_impl(tsd, buf);
325 	}
326 #undef THREAD_NAME_MAX_LEN
327 }
328 
329 int
330 prof_getpid(void) {
331 #ifdef _WIN32
332 	return GetCurrentProcessId();
333 #else
334 	return getpid();
335 #endif
336 }
337 
338 /*
339  * This buffer is rather large for stack allocation, so use a single buffer for
340  * all profile dumps; protected by prof_dump_mtx.
341  */
342 static char prof_dump_buf[PROF_DUMP_BUFSIZE];
343 
344 typedef struct prof_dump_arg_s prof_dump_arg_t;
345 struct prof_dump_arg_s {
346 	/*
347 	 * Whether error should be handled locally: if true, then we print out
348 	 * error message as well as abort (if opt_abort is true) when an error
349 	 * occurred, and we also report the error back to the caller in the end;
350 	 * if false, then we only report the error back to the caller in the
351 	 * end.
352 	 */
353 	const bool handle_error_locally;
354 	/*
355 	 * Whether there has been an error in the dumping process, which could
356 	 * have happened either in file opening or in file writing.  When an
357 	 * error has already occurred, we will stop further writing to the file.
358 	 */
359 	bool error;
360 	/* File descriptor of the dump file. */
361 	int prof_dump_fd;
362 };
363 
364 static void
365 prof_dump_check_possible_error(prof_dump_arg_t *arg, bool err_cond,
366     const char *format, ...) {
367 	assert(!arg->error);
368 	if (!err_cond) {
369 		return;
370 	}
371 
372 	arg->error = true;
373 	if (!arg->handle_error_locally) {
374 		return;
375 	}
376 
377 	va_list ap;
378 	char buf[PROF_PRINTF_BUFSIZE];
379 	va_start(ap, format);
380 	malloc_vsnprintf(buf, sizeof(buf), format, ap);
381 	va_end(ap);
382 	malloc_write(buf);
383 
384 	if (opt_abort) {
385 		abort();
386 	}
387 }
388 
389 static int
390 prof_dump_open_file_impl(const char *filename, int mode) {
391 	return creat(filename, mode);
392 }
393 prof_dump_open_file_t *JET_MUTABLE prof_dump_open_file =
394     prof_dump_open_file_impl;
395 
396 static void
397 prof_dump_open(prof_dump_arg_t *arg, const char *filename) {
398 	arg->prof_dump_fd = prof_dump_open_file(filename, 0644);
399 	prof_dump_check_possible_error(arg, arg->prof_dump_fd == -1,
400 	    "<jemalloc>: failed to open \"%s\"\n", filename);
401 }
402 
403 prof_dump_write_file_t *JET_MUTABLE prof_dump_write_file = malloc_write_fd;
404 
405 static void
406 prof_dump_flush(void *opaque, const char *s) {
407 	cassert(config_prof);
408 	prof_dump_arg_t *arg = (prof_dump_arg_t *)opaque;
409 	if (!arg->error) {
410 		ssize_t err = prof_dump_write_file(arg->prof_dump_fd, s,
411 		    strlen(s));
412 		prof_dump_check_possible_error(arg, err == -1,
413 		    "<jemalloc>: failed to write during heap profile flush\n");
414 	}
415 }
416 
417 static void
418 prof_dump_close(prof_dump_arg_t *arg) {
419 	if (arg->prof_dump_fd != -1) {
420 		close(arg->prof_dump_fd);
421 	}
422 }
423 
424 #ifndef _WIN32
425 JEMALLOC_FORMAT_PRINTF(1, 2)
426 static int
427 prof_open_maps_internal(const char *format, ...) {
428 	int mfd;
429 	va_list ap;
430 	char filename[PATH_MAX + 1];
431 
432 	va_start(ap, format);
433 	malloc_vsnprintf(filename, sizeof(filename), format, ap);
434 	va_end(ap);
435 
436 #if defined(O_CLOEXEC)
437 	mfd = open(filename, O_RDONLY | O_CLOEXEC);
438 #else
439 	mfd = open(filename, O_RDONLY);
440 	if (mfd != -1) {
441 		fcntl(mfd, F_SETFD, fcntl(mfd, F_GETFD) | FD_CLOEXEC);
442 	}
443 #endif
444 
445 	return mfd;
446 }
447 #endif
448 
449 static int
450 prof_dump_open_maps_impl() {
451 	int mfd;
452 
453 	cassert(config_prof);
454 #if defined(__FreeBSD__) || defined(__DragonFly__)
455 	mfd = prof_open_maps_internal("/proc/curproc/map");
456 #elif defined(_WIN32)
457 	mfd = -1; // Not implemented
458 #else
459 	int pid = prof_getpid();
460 
461 	mfd = prof_open_maps_internal("/proc/%d/task/%d/maps", pid, pid);
462 	if (mfd == -1) {
463 		mfd = prof_open_maps_internal("/proc/%d/maps", pid);
464 	}
465 #endif
466 	return mfd;
467 }
468 prof_dump_open_maps_t *JET_MUTABLE prof_dump_open_maps =
469     prof_dump_open_maps_impl;
470 
471 static ssize_t
472 prof_dump_read_maps_cb(void *read_cbopaque, void *buf, size_t limit) {
473 	int mfd = *(int *)read_cbopaque;
474 	assert(mfd != -1);
475 	return malloc_read_fd(mfd, buf, limit);
476 }
477 
478 static void
479 prof_dump_maps(buf_writer_t *buf_writer) {
480 	int mfd = prof_dump_open_maps();
481 	if (mfd == -1) {
482 		return;
483 	}
484 
485 	buf_writer_cb(buf_writer, "\nMAPPED_LIBRARIES:\n");
486 	buf_writer_pipe(buf_writer, prof_dump_read_maps_cb, &mfd);
487 	close(mfd);
488 }
489 
490 static bool
491 prof_dump(tsd_t *tsd, bool propagate_err, const char *filename,
492     bool leakcheck) {
493 	cassert(config_prof);
494 	assert(tsd_reentrancy_level_get(tsd) == 0);
495 
496 	prof_tdata_t * tdata = prof_tdata_get(tsd, true);
497 	if (tdata == NULL) {
498 		return true;
499 	}
500 
501 	prof_dump_arg_t arg = {/* handle_error_locally */ !propagate_err,
502 	    /* error */ false, /* prof_dump_fd */ -1};
503 
504 	pre_reentrancy(tsd, NULL);
505 	malloc_mutex_lock(tsd_tsdn(tsd), &prof_dump_mtx);
506 
507 	prof_dump_open(&arg, filename);
508 	buf_writer_t buf_writer;
509 	bool err = buf_writer_init(tsd_tsdn(tsd), &buf_writer, prof_dump_flush,
510 	    &arg, prof_dump_buf, PROF_DUMP_BUFSIZE);
511 	assert(!err);
512 	prof_dump_impl(tsd, buf_writer_cb, &buf_writer, tdata, leakcheck);
513 	prof_dump_maps(&buf_writer);
514 	buf_writer_terminate(tsd_tsdn(tsd), &buf_writer);
515 	prof_dump_close(&arg);
516 
517 	prof_dump_hook_t dump_hook = prof_dump_hook_get();
518 	if (dump_hook != NULL) {
519 		dump_hook(filename);
520 	}
521 	malloc_mutex_unlock(tsd_tsdn(tsd), &prof_dump_mtx);
522 	post_reentrancy(tsd);
523 
524 	return arg.error;
525 }
526 
527 /*
528  * If profiling is off, then PROF_DUMP_FILENAME_LEN is 1, so we'll end up
529  * calling strncpy with a size of 0, which triggers a -Wstringop-truncation
530  * warning (strncpy can never actually be called in this case, since we bail out
531  * much earlier when config_prof is false).  This function works around the
532  * warning to let us leave the warning on.
533  */
534 static inline void
535 prof_strncpy(char *UNUSED dest, const char *UNUSED src, size_t UNUSED size) {
536 	cassert(config_prof);
537 #ifdef JEMALLOC_PROF
538 	strncpy(dest, src, size);
539 #endif
540 }
541 
542 static const char *
543 prof_prefix_get(tsdn_t* tsdn) {
544 	malloc_mutex_assert_owner(tsdn, &prof_dump_filename_mtx);
545 
546 	return prof_prefix == NULL ? opt_prof_prefix : prof_prefix;
547 }
548 
549 static bool
550 prof_prefix_is_empty(tsdn_t *tsdn) {
551 	malloc_mutex_lock(tsdn, &prof_dump_filename_mtx);
552 	bool ret = (prof_prefix_get(tsdn)[0] == '\0');
553 	malloc_mutex_unlock(tsdn, &prof_dump_filename_mtx);
554 	return ret;
555 }
556 
557 #define DUMP_FILENAME_BUFSIZE (PATH_MAX + 1)
558 #define VSEQ_INVALID UINT64_C(0xffffffffffffffff)
559 static void
560 prof_dump_filename(tsd_t *tsd, char *filename, char v, uint64_t vseq) {
561 	cassert(config_prof);
562 
563 	assert(tsd_reentrancy_level_get(tsd) == 0);
564 	const char *prefix = prof_prefix_get(tsd_tsdn(tsd));
565 
566 	if (vseq != VSEQ_INVALID) {
567 	        /* "<prefix>.<pid>.<seq>.v<vseq>.heap" */
568 		malloc_snprintf(filename, DUMP_FILENAME_BUFSIZE,
569 		    "%s.%d.%"FMTu64".%c%"FMTu64".heap", prefix, prof_getpid(),
570 		    prof_dump_seq, v, vseq);
571 	} else {
572 	        /* "<prefix>.<pid>.<seq>.<v>.heap" */
573 		malloc_snprintf(filename, DUMP_FILENAME_BUFSIZE,
574 		    "%s.%d.%"FMTu64".%c.heap", prefix, prof_getpid(),
575 		    prof_dump_seq, v);
576 	}
577 	prof_dump_seq++;
578 }
579 
580 void
581 prof_get_default_filename(tsdn_t *tsdn, char *filename, uint64_t ind) {
582 	malloc_mutex_lock(tsdn, &prof_dump_filename_mtx);
583 	malloc_snprintf(filename, PROF_DUMP_FILENAME_LEN,
584 	    "%s.%d.%"FMTu64".json", prof_prefix_get(tsdn), prof_getpid(), ind);
585 	malloc_mutex_unlock(tsdn, &prof_dump_filename_mtx);
586 }
587 
588 void
589 prof_fdump_impl(tsd_t *tsd) {
590 	char filename[DUMP_FILENAME_BUFSIZE];
591 
592 	assert(!prof_prefix_is_empty(tsd_tsdn(tsd)));
593 	malloc_mutex_lock(tsd_tsdn(tsd), &prof_dump_filename_mtx);
594 	prof_dump_filename(tsd, filename, 'f', VSEQ_INVALID);
595 	malloc_mutex_unlock(tsd_tsdn(tsd), &prof_dump_filename_mtx);
596 	prof_dump(tsd, false, filename, opt_prof_leak);
597 }
598 
599 bool
600 prof_prefix_set(tsdn_t *tsdn, const char *prefix) {
601 	cassert(config_prof);
602 	ctl_mtx_assert_held(tsdn);
603 	malloc_mutex_lock(tsdn, &prof_dump_filename_mtx);
604 	if (prof_prefix == NULL) {
605 		malloc_mutex_unlock(tsdn, &prof_dump_filename_mtx);
606 		/* Everything is still guarded by ctl_mtx. */
607 		char *buffer = base_alloc(tsdn, prof_base,
608 		    PROF_DUMP_FILENAME_LEN, QUANTUM);
609 		if (buffer == NULL) {
610 			return true;
611 		}
612 		malloc_mutex_lock(tsdn, &prof_dump_filename_mtx);
613 		prof_prefix = buffer;
614 	}
615 	assert(prof_prefix != NULL);
616 
617 	prof_strncpy(prof_prefix, prefix, PROF_DUMP_FILENAME_LEN - 1);
618 	prof_prefix[PROF_DUMP_FILENAME_LEN - 1] = '\0';
619 	malloc_mutex_unlock(tsdn, &prof_dump_filename_mtx);
620 
621 	return false;
622 }
623 
624 void
625 prof_idump_impl(tsd_t *tsd) {
626 	malloc_mutex_lock(tsd_tsdn(tsd), &prof_dump_filename_mtx);
627 	if (prof_prefix_get(tsd_tsdn(tsd))[0] == '\0') {
628 		malloc_mutex_unlock(tsd_tsdn(tsd), &prof_dump_filename_mtx);
629 		return;
630 	}
631 	char filename[PATH_MAX + 1];
632 	prof_dump_filename(tsd, filename, 'i', prof_dump_iseq);
633 	prof_dump_iseq++;
634 	malloc_mutex_unlock(tsd_tsdn(tsd), &prof_dump_filename_mtx);
635 	prof_dump(tsd, false, filename, false);
636 }
637 
638 bool
639 prof_mdump_impl(tsd_t *tsd, const char *filename) {
640 	char filename_buf[DUMP_FILENAME_BUFSIZE];
641 	if (filename == NULL) {
642 		/* No filename specified, so automatically generate one. */
643 		malloc_mutex_lock(tsd_tsdn(tsd), &prof_dump_filename_mtx);
644 		if (prof_prefix_get(tsd_tsdn(tsd))[0] == '\0') {
645 			malloc_mutex_unlock(tsd_tsdn(tsd), &prof_dump_filename_mtx);
646 			return true;
647 		}
648 		prof_dump_filename(tsd, filename_buf, 'm', prof_dump_mseq);
649 		prof_dump_mseq++;
650 		malloc_mutex_unlock(tsd_tsdn(tsd), &prof_dump_filename_mtx);
651 		filename = filename_buf;
652 	}
653 	return prof_dump(tsd, true, filename, false);
654 }
655 
656 void
657 prof_gdump_impl(tsd_t *tsd) {
658 	tsdn_t *tsdn = tsd_tsdn(tsd);
659 	malloc_mutex_lock(tsdn, &prof_dump_filename_mtx);
660 	if (prof_prefix_get(tsdn)[0] == '\0') {
661 		malloc_mutex_unlock(tsdn, &prof_dump_filename_mtx);
662 		return;
663 	}
664 	char filename[DUMP_FILENAME_BUFSIZE];
665 	prof_dump_filename(tsd, filename, 'u', prof_dump_useq);
666 	prof_dump_useq++;
667 	malloc_mutex_unlock(tsdn, &prof_dump_filename_mtx);
668 	prof_dump(tsd, false, filename, false);
669 }
670