Allow setting a dump hook
If users want to be notified when a heap dump occurs, they can set this hook.
This commit is contained in:
parent
f7d46b8119
commit
a9031a0970
@ -48,14 +48,12 @@ extern size_t lg_prof_sample;
|
|||||||
|
|
||||||
extern bool prof_booted;
|
extern bool prof_booted;
|
||||||
|
|
||||||
/*
|
|
||||||
* A hook to mock out backtrace functionality. This can be handy, since it's
|
|
||||||
* otherwise difficult to guarantee that two allocations are reported as coming
|
|
||||||
* from the exact same stack trace in the presence of an optimizing compiler.
|
|
||||||
*/
|
|
||||||
void prof_backtrace_hook_set(prof_backtrace_hook_t hook);
|
void prof_backtrace_hook_set(prof_backtrace_hook_t hook);
|
||||||
prof_backtrace_hook_t prof_backtrace_hook_get();
|
prof_backtrace_hook_t prof_backtrace_hook_get();
|
||||||
|
|
||||||
|
void prof_dump_hook_set(prof_dump_hook_t hook);
|
||||||
|
prof_dump_hook_t prof_dump_hook_get();
|
||||||
|
|
||||||
/* Functions only accessed in prof_inlines.h */
|
/* Functions only accessed in prof_inlines.h */
|
||||||
prof_tdata_t *prof_tdata_init(tsd_t *tsd);
|
prof_tdata_t *prof_tdata_init(tsd_t *tsd);
|
||||||
prof_tdata_t *prof_tdata_reinit(tsd_t *tsd, prof_tdata_t *tdata);
|
prof_tdata_t *prof_tdata_reinit(tsd_t *tsd, prof_tdata_t *tdata);
|
||||||
|
@ -13,4 +13,9 @@
|
|||||||
*/
|
*/
|
||||||
typedef void (*prof_backtrace_hook_t)(void **, unsigned *, unsigned);
|
typedef void (*prof_backtrace_hook_t)(void **, unsigned *, unsigned);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A callback hook that notifies about recently dumped heap profile.
|
||||||
|
*/
|
||||||
|
typedef void (*prof_dump_hook_t)(const char *filename);
|
||||||
|
|
||||||
#endif /* JEMALLOC_INTERNAL_PROF_HOOK_H */
|
#endif /* JEMALLOC_INTERNAL_PROF_HOOK_H */
|
||||||
|
32
src/ctl.c
32
src/ctl.c
@ -306,6 +306,7 @@ CTL_PROTO(stats_zero_reallocs)
|
|||||||
CTL_PROTO(experimental_hooks_install)
|
CTL_PROTO(experimental_hooks_install)
|
||||||
CTL_PROTO(experimental_hooks_remove)
|
CTL_PROTO(experimental_hooks_remove)
|
||||||
CTL_PROTO(experimental_hooks_prof_backtrace)
|
CTL_PROTO(experimental_hooks_prof_backtrace)
|
||||||
|
CTL_PROTO(experimental_hooks_prof_dump)
|
||||||
CTL_PROTO(experimental_thread_activity_callback)
|
CTL_PROTO(experimental_thread_activity_callback)
|
||||||
CTL_PROTO(experimental_utilization_query)
|
CTL_PROTO(experimental_utilization_query)
|
||||||
CTL_PROTO(experimental_utilization_batch_query)
|
CTL_PROTO(experimental_utilization_batch_query)
|
||||||
@ -835,7 +836,8 @@ static const ctl_named_node_t stats_node[] = {
|
|||||||
static const ctl_named_node_t experimental_hooks_node[] = {
|
static const ctl_named_node_t experimental_hooks_node[] = {
|
||||||
{NAME("install"), CTL(experimental_hooks_install)},
|
{NAME("install"), CTL(experimental_hooks_install)},
|
||||||
{NAME("remove"), CTL(experimental_hooks_remove)},
|
{NAME("remove"), CTL(experimental_hooks_remove)},
|
||||||
{NAME("prof_backtrace"), CTL(experimental_hooks_prof_backtrace)}
|
{NAME("prof_backtrace"), CTL(experimental_hooks_prof_backtrace)},
|
||||||
|
{NAME("prof_dump"), CTL(experimental_hooks_prof_dump)},
|
||||||
};
|
};
|
||||||
|
|
||||||
static const ctl_named_node_t experimental_thread_node[] = {
|
static const ctl_named_node_t experimental_thread_node[] = {
|
||||||
@ -3362,6 +3364,34 @@ label_return:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
experimental_hooks_prof_dump_ctl(tsd_t *tsd, const size_t *mib,
|
||||||
|
size_t miblen, void *oldp, size_t *oldlenp, void *newp, size_t newlen) {
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (oldp == NULL && newp == NULL) {
|
||||||
|
ret = EINVAL;
|
||||||
|
goto label_return;
|
||||||
|
}
|
||||||
|
if (oldp != NULL) {
|
||||||
|
prof_dump_hook_t old_hook =
|
||||||
|
prof_dump_hook_get();
|
||||||
|
READ(old_hook, prof_dump_hook_t);
|
||||||
|
}
|
||||||
|
if (newp != NULL) {
|
||||||
|
if (!opt_prof) {
|
||||||
|
ret = ENOENT;
|
||||||
|
goto label_return;
|
||||||
|
}
|
||||||
|
prof_dump_hook_t new_hook JEMALLOC_CC_SILENCE_INIT(NULL);
|
||||||
|
WRITE(new_hook, prof_dump_hook_t);
|
||||||
|
prof_dump_hook_set(new_hook);
|
||||||
|
}
|
||||||
|
ret = 0;
|
||||||
|
label_return:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
CTL_RO_CGEN(config_stats, stats_allocated, ctl_stats->allocated, size_t)
|
CTL_RO_CGEN(config_stats, stats_allocated, ctl_stats->allocated, size_t)
|
||||||
|
16
src/prof.c
16
src/prof.c
@ -73,6 +73,9 @@ bool prof_booted = false;
|
|||||||
/* Logically a prof_backtrace_hook_t. */
|
/* Logically a prof_backtrace_hook_t. */
|
||||||
atomic_p_t prof_backtrace_hook;
|
atomic_p_t prof_backtrace_hook;
|
||||||
|
|
||||||
|
/* Logically a prof_dump_hook_t. */
|
||||||
|
atomic_p_t prof_dump_hook;
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
void
|
void
|
||||||
@ -533,6 +536,17 @@ prof_backtrace_hook_get() {
|
|||||||
ATOMIC_ACQUIRE);
|
ATOMIC_ACQUIRE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
prof_dump_hook_set(prof_dump_hook_t hook) {
|
||||||
|
atomic_store_p(&prof_dump_hook, hook, ATOMIC_RELEASE);
|
||||||
|
}
|
||||||
|
|
||||||
|
prof_dump_hook_t
|
||||||
|
prof_dump_hook_get() {
|
||||||
|
return (prof_dump_hook_t)atomic_load_p(&prof_dump_hook,
|
||||||
|
ATOMIC_ACQUIRE);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
prof_boot0(void) {
|
prof_boot0(void) {
|
||||||
cassert(config_prof);
|
cassert(config_prof);
|
||||||
@ -672,8 +686,8 @@ prof_boot2(tsd_t *tsd, base_t *base) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
prof_hooks_init();
|
|
||||||
prof_unwind_init();
|
prof_unwind_init();
|
||||||
|
prof_hooks_init();
|
||||||
}
|
}
|
||||||
prof_booted = true;
|
prof_booted = true;
|
||||||
|
|
||||||
|
@ -55,6 +55,7 @@ prof_backtrace_impl(void **vec, unsigned *len, unsigned max_len) {
|
|||||||
cassert(config_prof);
|
cassert(config_prof);
|
||||||
assert(*len == 0);
|
assert(*len == 0);
|
||||||
assert(vec != NULL);
|
assert(vec != NULL);
|
||||||
|
assert(max_len == PROF_BT_MAX);
|
||||||
|
|
||||||
nframes = unw_backtrace(vec, PROF_BT_MAX);
|
nframes = unw_backtrace(vec, PROF_BT_MAX);
|
||||||
if (nframes <= 0) {
|
if (nframes <= 0) {
|
||||||
@ -95,6 +96,8 @@ prof_backtrace_impl(void **vec, unsigned *len, unsigned max_len) {
|
|||||||
prof_unwind_data_t data = {vec, len, max_len};
|
prof_unwind_data_t data = {vec, len, max_len};
|
||||||
|
|
||||||
cassert(config_prof);
|
cassert(config_prof);
|
||||||
|
assert(vec != NULL);
|
||||||
|
assert(max_len == PROF_BT_MAX);
|
||||||
|
|
||||||
_Unwind_Backtrace(prof_unwind_callback, &data);
|
_Unwind_Backtrace(prof_unwind_callback, &data);
|
||||||
}
|
}
|
||||||
@ -118,6 +121,8 @@ prof_backtrace_impl(void **vec, unsigned *len, unsigned max_len) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cassert(config_prof);
|
cassert(config_prof);
|
||||||
|
assert(vec != NULL);
|
||||||
|
assert(max_len == PROF_BT_MAX);
|
||||||
|
|
||||||
BT_FRAME(0)
|
BT_FRAME(0)
|
||||||
BT_FRAME(1)
|
BT_FRAME(1)
|
||||||
@ -272,8 +277,10 @@ prof_backtrace_impl(void **vec, unsigned *len, unsigned max_len) {
|
|||||||
void
|
void
|
||||||
prof_backtrace(tsd_t *tsd, prof_bt_t *bt) {
|
prof_backtrace(tsd_t *tsd, prof_bt_t *bt) {
|
||||||
cassert(config_prof);
|
cassert(config_prof);
|
||||||
pre_reentrancy(tsd, NULL);
|
|
||||||
prof_backtrace_hook_t prof_backtrace_hook = prof_backtrace_hook_get();
|
prof_backtrace_hook_t prof_backtrace_hook = prof_backtrace_hook_get();
|
||||||
|
assert(prof_backtrace_hook != NULL);
|
||||||
|
|
||||||
|
pre_reentrancy(tsd, NULL);
|
||||||
prof_backtrace_hook(bt->vec, &bt->len, PROF_BT_MAX);
|
prof_backtrace_hook(bt->vec, &bt->len, PROF_BT_MAX);
|
||||||
post_reentrancy(tsd);
|
post_reentrancy(tsd);
|
||||||
}
|
}
|
||||||
@ -281,6 +288,7 @@ prof_backtrace(tsd_t *tsd, prof_bt_t *bt) {
|
|||||||
void
|
void
|
||||||
prof_hooks_init() {
|
prof_hooks_init() {
|
||||||
prof_backtrace_hook_set(&prof_backtrace_impl);
|
prof_backtrace_hook_set(&prof_backtrace_impl);
|
||||||
|
prof_dump_hook_set(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
@ -506,6 +514,10 @@ prof_dump(tsd_t *tsd, bool propagate_err, const char *filename,
|
|||||||
buf_writer_terminate(tsd_tsdn(tsd), &buf_writer);
|
buf_writer_terminate(tsd_tsdn(tsd), &buf_writer);
|
||||||
prof_dump_close(&arg);
|
prof_dump_close(&arg);
|
||||||
|
|
||||||
|
prof_dump_hook_t dump_hook = prof_dump_hook_get();
|
||||||
|
if (dump_hook != NULL) {
|
||||||
|
dump_hook(filename);
|
||||||
|
}
|
||||||
malloc_mutex_unlock(tsd_tsdn(tsd), &prof_dump_mtx);
|
malloc_mutex_unlock(tsd_tsdn(tsd), &prof_dump_mtx);
|
||||||
post_reentrancy(tsd);
|
post_reentrancy(tsd);
|
||||||
|
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
#include "test/jemalloc_test.h"
|
#include "test/jemalloc_test.h"
|
||||||
|
|
||||||
|
const char *dump_filename = "/dev/null";
|
||||||
|
|
||||||
|
prof_backtrace_hook_t default_hook;
|
||||||
|
|
||||||
bool mock_bt_hook_called = false;
|
bool mock_bt_hook_called = false;
|
||||||
|
bool mock_dump_hook_called = false;
|
||||||
|
|
||||||
void
|
void
|
||||||
mock_bt_hook(void **vec, unsigned *len, unsigned max_len) {
|
mock_bt_hook(void **vec, unsigned *len, unsigned max_len) {
|
||||||
@ -11,7 +16,38 @@ mock_bt_hook(void **vec, unsigned *len, unsigned max_len) {
|
|||||||
mock_bt_hook_called = true;
|
mock_bt_hook_called = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_BEGIN(test_prof_backtrace_hook) {
|
void
|
||||||
|
mock_bt_augmenting_hook(void **vec, unsigned *len, unsigned max_len) {
|
||||||
|
default_hook(vec, len, max_len);
|
||||||
|
expect_u_gt(*len, 0, "Default backtrace hook returned empty backtrace");
|
||||||
|
expect_u_lt(*len, max_len,
|
||||||
|
"Default backtrace hook returned too large backtrace");
|
||||||
|
|
||||||
|
/* Add a separator between default frames and augmented */
|
||||||
|
vec[*len] = (void *)0x030303030;
|
||||||
|
(*len)++;
|
||||||
|
|
||||||
|
/* Add more stack frames */
|
||||||
|
for (unsigned i = 0; i < 3; ++i) {
|
||||||
|
if (*len == max_len) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
vec[*len] = (void *)((uintptr_t)i);
|
||||||
|
(*len)++;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
mock_bt_hook_called = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mock_dump_hook(const char *filename) {
|
||||||
|
mock_dump_hook_called = true;
|
||||||
|
expect_str_eq(filename, dump_filename,
|
||||||
|
"Incorrect file name passed to the dump hook");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_BEGIN(test_prof_backtrace_hook_replace) {
|
||||||
|
|
||||||
test_skip_if(!config_prof);
|
test_skip_if(!config_prof);
|
||||||
|
|
||||||
@ -27,7 +63,6 @@ TEST_BEGIN(test_prof_backtrace_hook) {
|
|||||||
NULL, 0, (void *)&null_hook, sizeof(null_hook)),
|
NULL, 0, (void *)&null_hook, sizeof(null_hook)),
|
||||||
EINVAL, "Incorrectly allowed NULL backtrace hook");
|
EINVAL, "Incorrectly allowed NULL backtrace hook");
|
||||||
|
|
||||||
prof_backtrace_hook_t default_hook;
|
|
||||||
size_t default_hook_sz = sizeof(prof_backtrace_hook_t);
|
size_t default_hook_sz = sizeof(prof_backtrace_hook_t);
|
||||||
prof_backtrace_hook_t hook = &mock_bt_hook;
|
prof_backtrace_hook_t hook = &mock_bt_hook;
|
||||||
expect_d_eq(mallctl("experimental.hooks.prof_backtrace",
|
expect_d_eq(mallctl("experimental.hooks.prof_backtrace",
|
||||||
@ -54,8 +89,81 @@ TEST_BEGIN(test_prof_backtrace_hook) {
|
|||||||
}
|
}
|
||||||
TEST_END
|
TEST_END
|
||||||
|
|
||||||
|
TEST_BEGIN(test_prof_backtrace_hook_augment) {
|
||||||
|
|
||||||
|
test_skip_if(!config_prof);
|
||||||
|
|
||||||
|
mock_bt_hook_called = false;
|
||||||
|
|
||||||
|
void *p0 = mallocx(1, 0);
|
||||||
|
assert_ptr_not_null(p0, "Failed to allocate");
|
||||||
|
|
||||||
|
expect_false(mock_bt_hook_called, "Called mock hook before it's set");
|
||||||
|
|
||||||
|
size_t default_hook_sz = sizeof(prof_backtrace_hook_t);
|
||||||
|
prof_backtrace_hook_t hook = &mock_bt_augmenting_hook;
|
||||||
|
expect_d_eq(mallctl("experimental.hooks.prof_backtrace",
|
||||||
|
(void *)&default_hook, &default_hook_sz, (void *)&hook,
|
||||||
|
sizeof(hook)), 0, "Unexpected mallctl failure setting hook");
|
||||||
|
|
||||||
|
void *p1 = mallocx(1, 0);
|
||||||
|
assert_ptr_not_null(p1, "Failed to allocate");
|
||||||
|
|
||||||
|
expect_true(mock_bt_hook_called, "Didn't call mock hook");
|
||||||
|
|
||||||
|
prof_backtrace_hook_t current_hook;
|
||||||
|
size_t current_hook_sz = sizeof(prof_backtrace_hook_t);
|
||||||
|
expect_d_eq(mallctl("experimental.hooks.prof_backtrace",
|
||||||
|
(void *)¤t_hook, ¤t_hook_sz, (void *)&default_hook,
|
||||||
|
sizeof(default_hook)), 0,
|
||||||
|
"Unexpected mallctl failure resetting hook to default");
|
||||||
|
|
||||||
|
expect_ptr_eq(current_hook, hook,
|
||||||
|
"Hook returned by mallctl is not equal to mock hook");
|
||||||
|
|
||||||
|
dallocx(p1, 0);
|
||||||
|
dallocx(p0, 0);
|
||||||
|
}
|
||||||
|
TEST_END
|
||||||
|
|
||||||
|
TEST_BEGIN(test_prof_dump_hook) {
|
||||||
|
|
||||||
|
test_skip_if(!config_prof);
|
||||||
|
|
||||||
|
mock_dump_hook_called = false;
|
||||||
|
|
||||||
|
expect_d_eq(mallctl("prof.dump", NULL, NULL, (void *)&dump_filename,
|
||||||
|
sizeof(dump_filename)), 0, "Failed to dump heap profile");
|
||||||
|
|
||||||
|
expect_false(mock_dump_hook_called, "Called dump hook before it's set");
|
||||||
|
|
||||||
|
size_t default_hook_sz = sizeof(prof_dump_hook_t);
|
||||||
|
prof_dump_hook_t hook = &mock_dump_hook;
|
||||||
|
expect_d_eq(mallctl("experimental.hooks.prof_dump",
|
||||||
|
(void *)&default_hook, &default_hook_sz, (void *)&hook,
|
||||||
|
sizeof(hook)), 0, "Unexpected mallctl failure setting hook");
|
||||||
|
|
||||||
|
expect_d_eq(mallctl("prof.dump", NULL, NULL, (void *)&dump_filename,
|
||||||
|
sizeof(dump_filename)), 0, "Failed to dump heap profile");
|
||||||
|
|
||||||
|
expect_true(mock_dump_hook_called, "Didn't call mock hook");
|
||||||
|
|
||||||
|
prof_dump_hook_t current_hook;
|
||||||
|
size_t current_hook_sz = sizeof(prof_dump_hook_t);
|
||||||
|
expect_d_eq(mallctl("experimental.hooks.prof_dump",
|
||||||
|
(void *)¤t_hook, ¤t_hook_sz, (void *)&default_hook,
|
||||||
|
sizeof(default_hook)), 0,
|
||||||
|
"Unexpected mallctl failure resetting hook to default");
|
||||||
|
|
||||||
|
expect_ptr_eq(current_hook, hook,
|
||||||
|
"Hook returned by mallctl is not equal to mock hook");
|
||||||
|
}
|
||||||
|
TEST_END
|
||||||
|
|
||||||
int
|
int
|
||||||
main(void) {
|
main(void) {
|
||||||
return test(
|
return test(
|
||||||
test_prof_backtrace_hook);
|
test_prof_backtrace_hook_replace,
|
||||||
|
test_prof_backtrace_hook_augment,
|
||||||
|
test_prof_dump_hook);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user