Add stats counters for number of zero reallocs

This commit is contained in:
David T. Goldblatt 2019-10-26 11:04:46 -07:00 committed by David Goldblatt
parent 9cfa805947
commit de81a4eada
8 changed files with 81 additions and 2 deletions

View File

@ -235,7 +235,8 @@ TESTS_UNIT := \
$(srcroot)test/unit/zero.c \ $(srcroot)test/unit/zero.c \
$(srcroot)test/unit/zero_realloc_abort.c \ $(srcroot)test/unit/zero_realloc_abort.c \
$(srcroot)test/unit/zero_realloc_free.c \ $(srcroot)test/unit/zero_realloc_free.c \
$(srcroot)test/unit/zero_realloc_strict.c $(srcroot)test/unit/zero_realloc_strict.c \
$(srcroot)test/unit/zero_reallocs.c
ifeq (@enable_prof@, 1) ifeq (@enable_prof@, 1)
TESTS_UNIT += \ TESTS_UNIT += \
$(srcroot)test/unit/arena_reset_prof.c $(srcroot)test/unit/arena_reset_prof.c

View File

@ -2451,6 +2451,21 @@ struct extent_hooks_s {
</para></listitem> </para></listitem>
</varlistentry> </varlistentry>
<varlistentry id="stats.zero_reallocs">
<term>
<mallctl>stats.zero_reallocs</mallctl>
(<type>size_t</type>)
<literal>r-</literal>
[<option>--enable-stats</option>]
</term>
<listitem><para>Number of times that the <function>realloc()</function>
was called with a non-<constant>NULL</constant> pointer argument and a
<constant>0</constant> size argument. This is a fundamentally unsafe
pattern in portable programs; see <link linkend="opt.zero_realloc">
<mallctl>opt.zero_realloc</mallctl></link> for details.
</para></listitem>
</varlistentry>
<varlistentry id="stats.background_thread.num_threads"> <varlistentry id="stats.background_thread.num_threads">
<term> <term>
<mallctl>stats.background_thread.num_threads</mallctl> <mallctl>stats.background_thread.num_threads</mallctl>

View File

@ -20,6 +20,7 @@ extern bool opt_zero;
extern unsigned opt_narenas; extern unsigned opt_narenas;
extern zero_realloc_action_t opt_zero_realloc_action; extern zero_realloc_action_t opt_zero_realloc_action;
extern const char *zero_realloc_mode_names[]; extern const char *zero_realloc_mode_names[];
extern atomic_zu_t zero_realloc_count;
/* Number of CPUs. */ /* Number of CPUs. */
extern unsigned ncpus; extern unsigned ncpus;

View File

@ -224,6 +224,7 @@ CTL_PROTO(stats_metadata_thp)
CTL_PROTO(stats_resident) CTL_PROTO(stats_resident)
CTL_PROTO(stats_mapped) CTL_PROTO(stats_mapped)
CTL_PROTO(stats_retained) CTL_PROTO(stats_retained)
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_utilization_query) CTL_PROTO(experimental_utilization_query)
@ -593,7 +594,8 @@ static const ctl_named_node_t stats_node[] = {
{NAME("background_thread"), {NAME("background_thread"),
CHILD(named, stats_background_thread)}, CHILD(named, stats_background_thread)},
{NAME("mutexes"), CHILD(named, stats_mutexes)}, {NAME("mutexes"), CHILD(named, stats_mutexes)},
{NAME("arenas"), CHILD(indexed, stats_arenas)} {NAME("arenas"), CHILD(indexed, stats_arenas)},
{NAME("zero_reallocs"), CTL(stats_zero_reallocs)},
}; };
static const ctl_named_node_t experimental_hooks_node[] = { static const ctl_named_node_t experimental_hooks_node[] = {
@ -2841,6 +2843,9 @@ CTL_RO_CGEN(config_stats, stats_background_thread_num_runs,
CTL_RO_CGEN(config_stats, stats_background_thread_run_interval, CTL_RO_CGEN(config_stats, stats_background_thread_run_interval,
nstime_ns(&ctl_stats->background_thread.run_interval), uint64_t) nstime_ns(&ctl_stats->background_thread.run_interval), uint64_t)
CTL_RO_CGEN(config_stats, stats_zero_reallocs,
atomic_load_zu(&zero_realloc_count, ATOMIC_RELAXED), size_t)
CTL_RO_GEN(stats_arenas_i_dss, arenas_i(mib[2])->dss, const char *) CTL_RO_GEN(stats_arenas_i_dss, arenas_i(mib[2])->dss, const char *)
CTL_RO_GEN(stats_arenas_i_dirty_decay_ms, arenas_i(mib[2])->dirty_decay_ms, CTL_RO_GEN(stats_arenas_i_dirty_decay_ms, arenas_i(mib[2])->dirty_decay_ms,
ssize_t) ssize_t)

View File

@ -70,6 +70,8 @@ bool opt_junk_free =
zero_realloc_action_t opt_zero_realloc_action = zero_realloc_action_t opt_zero_realloc_action =
zero_realloc_action_strict; zero_realloc_action_strict;
atomic_zu_t zero_realloc_count = ATOMIC_INIT(0);
const char *zero_realloc_mode_names[] = { const char *zero_realloc_mode_names[] = {
"strict", "strict",
"free", "free",
@ -3160,6 +3162,9 @@ je_rallocx(void *ptr, size_t size, int flags) {
static void * static void *
do_realloc_nonnull_zero(void *ptr) { do_realloc_nonnull_zero(void *ptr) {
if (config_stats) {
atomic_fetch_add_zu(&zero_realloc_count, 1, ATOMIC_RELAXED);
}
if (opt_zero_realloc_action == zero_realloc_action_strict) { if (opt_zero_realloc_action == zero_realloc_action_strict) {
/* /*
* The user might have gotten a strict setting while expecting a * The user might have gotten a strict setting while expecting a

View File

@ -1252,6 +1252,7 @@ stats_print_helper(emitter_t *emitter, bool merged, bool destroyed,
size_t allocated, active, metadata, metadata_thp, resident, mapped, size_t allocated, active, metadata, metadata_thp, resident, mapped,
retained; retained;
size_t num_background_threads; size_t num_background_threads;
size_t zero_reallocs;
uint64_t background_thread_num_runs, background_thread_run_interval; uint64_t background_thread_num_runs, background_thread_run_interval;
CTL_GET("stats.allocated", &allocated, size_t); CTL_GET("stats.allocated", &allocated, size_t);
@ -1262,6 +1263,8 @@ stats_print_helper(emitter_t *emitter, bool merged, bool destroyed,
CTL_GET("stats.mapped", &mapped, size_t); CTL_GET("stats.mapped", &mapped, size_t);
CTL_GET("stats.retained", &retained, size_t); CTL_GET("stats.retained", &retained, size_t);
CTL_GET("stats.zero_reallocs", &zero_reallocs, size_t);
if (have_background_thread) { if (have_background_thread) {
CTL_GET("stats.background_thread.num_threads", CTL_GET("stats.background_thread.num_threads",
&num_background_threads, size_t); &num_background_threads, size_t);
@ -1285,12 +1288,18 @@ stats_print_helper(emitter_t *emitter, bool merged, bool destroyed,
emitter_json_kv(emitter, "resident", emitter_type_size, &resident); emitter_json_kv(emitter, "resident", emitter_type_size, &resident);
emitter_json_kv(emitter, "mapped", emitter_type_size, &mapped); emitter_json_kv(emitter, "mapped", emitter_type_size, &mapped);
emitter_json_kv(emitter, "retained", emitter_type_size, &retained); emitter_json_kv(emitter, "retained", emitter_type_size, &retained);
emitter_json_kv(emitter, "zero_reallocs", emitter_type_size,
&zero_reallocs);
emitter_table_printf(emitter, "Allocated: %zu, active: %zu, " emitter_table_printf(emitter, "Allocated: %zu, active: %zu, "
"metadata: %zu (n_thp %zu), resident: %zu, mapped: %zu, " "metadata: %zu (n_thp %zu), resident: %zu, mapped: %zu, "
"retained: %zu\n", allocated, active, metadata, metadata_thp, "retained: %zu\n", allocated, active, metadata, metadata_thp,
resident, mapped, retained); resident, mapped, retained);
/* Strange behaviors */
emitter_table_printf(emitter,
"Count of realloc(non-null-ptr, 0) calls: %zu\n", zero_reallocs);
/* Background thread stats. */ /* Background thread stats. */
emitter_json_object_kv_begin(emitter, "background_thread"); emitter_json_object_kv_begin(emitter, "background_thread");
emitter_json_kv(emitter, "num_threads", emitter_type_size, emitter_json_kv(emitter, "num_threads", emitter_type_size,

40
test/unit/zero_reallocs.c Normal file
View File

@ -0,0 +1,40 @@
#include "test/jemalloc_test.h"
static size_t
zero_reallocs() {
if (!config_stats) {
return 0;
}
size_t count = 12345;
size_t sz = sizeof(count);
assert_d_eq(mallctl("stats.zero_reallocs", (void *)&count, &sz,
NULL, 0), 0, "Unexpected mallctl failure");
return count;
}
TEST_BEGIN(test_zero_reallocs) {
test_skip_if(!config_stats);
for (size_t i = 0; i < 100; ++i) {
void *ptr = mallocx(i * i + 1, 0);
assert_ptr_not_null(ptr, "Unexpected mallocx error");
size_t count = zero_reallocs();
assert_zu_eq(i, count, "Incorrect zero realloc count");
ptr = realloc(ptr, 0);
assert_ptr_null(ptr, "Realloc didn't free");
count = zero_reallocs();
assert_zu_eq(i + 1, count, "Realloc didn't adjust count");
}
}
TEST_END
int
main(void) {
/*
* We expect explicit counts; reentrant tests run multiple times, so
* counts leak across runs.
*/
return test_no_reentrancy(
test_zero_reallocs);
}

View File

@ -0,0 +1,3 @@
#!/bin/sh
export MALLOC_CONF="zero_realloc:free"