Add mallctl for dumping last-N profiling records
This commit is contained in:
parent
bc05ecebf6
commit
68e8ddcaff
33
src/ctl.c
33
src/ctl.c
@ -237,6 +237,7 @@ CTL_PROTO(experimental_utilization_batch_query)
|
|||||||
CTL_PROTO(experimental_arenas_i_pactivep)
|
CTL_PROTO(experimental_arenas_i_pactivep)
|
||||||
INDEX_PROTO(experimental_arenas_i)
|
INDEX_PROTO(experimental_arenas_i)
|
||||||
CTL_PROTO(experimental_prof_recent_alloc_max)
|
CTL_PROTO(experimental_prof_recent_alloc_max)
|
||||||
|
CTL_PROTO(experimental_prof_recent_alloc_dump)
|
||||||
|
|
||||||
#define MUTEX_STATS_CTL_PROTO_GEN(n) \
|
#define MUTEX_STATS_CTL_PROTO_GEN(n) \
|
||||||
CTL_PROTO(stats_##n##_num_ops) \
|
CTL_PROTO(stats_##n##_num_ops) \
|
||||||
@ -631,6 +632,7 @@ static const ctl_indexed_node_t experimental_arenas_node[] = {
|
|||||||
|
|
||||||
static const ctl_named_node_t experimental_prof_recent_node[] = {
|
static const ctl_named_node_t experimental_prof_recent_node[] = {
|
||||||
{NAME("alloc_max"), CTL(experimental_prof_recent_alloc_max)},
|
{NAME("alloc_max"), CTL(experimental_prof_recent_alloc_max)},
|
||||||
|
{NAME("alloc_dump"), CTL(experimental_prof_recent_alloc_dump)},
|
||||||
};
|
};
|
||||||
|
|
||||||
static const ctl_named_node_t experimental_node[] = {
|
static const ctl_named_node_t experimental_node[] = {
|
||||||
@ -3549,3 +3551,34 @@ experimental_prof_recent_alloc_max_ctl(tsd_t *tsd, const size_t *mib,
|
|||||||
label_return:
|
label_return:
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef struct write_cb_packet_s write_cb_packet_t;
|
||||||
|
struct write_cb_packet_s {
|
||||||
|
void (*write_cb)(void *, const char *);
|
||||||
|
void *cbopaque;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int
|
||||||
|
experimental_prof_recent_alloc_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 (!(config_prof && opt_prof)) {
|
||||||
|
ret = ENOENT;
|
||||||
|
goto label_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(sizeof(write_cb_packet_t) == sizeof(void *) * 2);
|
||||||
|
|
||||||
|
WRITEONLY();
|
||||||
|
write_cb_packet_t write_cb_packet;
|
||||||
|
ASSURED_WRITE(write_cb_packet, write_cb_packet_t);
|
||||||
|
|
||||||
|
prof_recent_alloc_dump(tsd, write_cb_packet.write_cb,
|
||||||
|
write_cb_packet.cbopaque);
|
||||||
|
|
||||||
|
ret = 0;
|
||||||
|
|
||||||
|
label_return:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
@ -381,6 +381,197 @@ TEST_END
|
|||||||
|
|
||||||
#undef NTH_REQ_SIZE
|
#undef NTH_REQ_SIZE
|
||||||
|
|
||||||
|
#define DUMP_OUT_SIZE 4096
|
||||||
|
static char dump_out[DUMP_OUT_SIZE];
|
||||||
|
static size_t dump_out_len = 0;
|
||||||
|
|
||||||
|
static void test_dump_write_cb(void *not_used, const char *str) {
|
||||||
|
size_t len = strlen(str);
|
||||||
|
assert(dump_out_len + len < DUMP_OUT_SIZE);
|
||||||
|
memcpy(dump_out + dump_out_len, str, len + 1);
|
||||||
|
dump_out_len += len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void call_dump() {
|
||||||
|
static void *in[2] = {test_dump_write_cb, NULL};
|
||||||
|
dump_out_len = 0;
|
||||||
|
assert_d_eq(mallctl("experimental.prof_recent.alloc_dump",
|
||||||
|
NULL, NULL, in, sizeof(in)), 0, "Dump mallctl raised error");
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
size_t size;
|
||||||
|
bool released;
|
||||||
|
} confirm_record_t;
|
||||||
|
|
||||||
|
#define DUMP_ERROR "Dump output is wrong"
|
||||||
|
|
||||||
|
static void confirm_record(const char *template,
|
||||||
|
const confirm_record_t *records, const size_t n_records) {
|
||||||
|
static const char *types[2] = {"alloc", "dalloc"};
|
||||||
|
static char buf[64];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The template string would be in the form of:
|
||||||
|
* "{\"recent_alloc_max\":XYZ,\"recent_alloc\":[]}",
|
||||||
|
* and dump_out would be in the form of:
|
||||||
|
* "{\"recent_alloc_max\":XYZ,\"recent_alloc\":[...]}".
|
||||||
|
* Using "- 2" serves to cut right before the ending "]}".
|
||||||
|
*/
|
||||||
|
assert_d_eq(memcmp(dump_out, template, strlen(template) - 2), 0,
|
||||||
|
DUMP_ERROR);
|
||||||
|
assert_d_eq(memcmp(dump_out + strlen(dump_out) - 2,
|
||||||
|
template + strlen(template) - 2, 2), 0, DUMP_ERROR);
|
||||||
|
|
||||||
|
const char *start = dump_out + strlen(template) - 2;
|
||||||
|
const char *end = dump_out + strlen(dump_out) - 2;
|
||||||
|
const confirm_record_t *record;
|
||||||
|
for (record = records; record < records + n_records; ++record) {
|
||||||
|
|
||||||
|
#define ASSERT_CHAR(c) do { \
|
||||||
|
assert_true(start < end, DUMP_ERROR); \
|
||||||
|
assert_c_eq(*start++, c, DUMP_ERROR); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define ASSERT_STR(s) do { \
|
||||||
|
const size_t len = strlen(s); \
|
||||||
|
assert_true(start + len <= end, DUMP_ERROR); \
|
||||||
|
assert_d_eq(memcmp(start, s, len), 0, DUMP_ERROR); \
|
||||||
|
start += len; \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define ASSERT_FORMATTED_STR(s, ...) do { \
|
||||||
|
malloc_snprintf(buf, sizeof(buf), s, __VA_ARGS__); \
|
||||||
|
ASSERT_STR(buf); \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
if (record != records) {
|
||||||
|
ASSERT_CHAR(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_CHAR('{');
|
||||||
|
|
||||||
|
ASSERT_STR("\"size\"");
|
||||||
|
ASSERT_CHAR(':');
|
||||||
|
ASSERT_FORMATTED_STR("%zu", record->size);
|
||||||
|
ASSERT_CHAR(',');
|
||||||
|
|
||||||
|
ASSERT_STR("\"usize\"");
|
||||||
|
ASSERT_CHAR(':');
|
||||||
|
ASSERT_FORMATTED_STR("%zu", sz_s2u(record->size));
|
||||||
|
ASSERT_CHAR(',');
|
||||||
|
|
||||||
|
ASSERT_STR("\"released\"");
|
||||||
|
ASSERT_CHAR(':');
|
||||||
|
ASSERT_STR(record->released ? "true" : "false");
|
||||||
|
ASSERT_CHAR(',');
|
||||||
|
|
||||||
|
const char **type = types;
|
||||||
|
while (true) {
|
||||||
|
ASSERT_FORMATTED_STR("\"%s_thread_uid\"", *type);
|
||||||
|
ASSERT_CHAR(':');
|
||||||
|
while (isdigit(*start)) {
|
||||||
|
++start;
|
||||||
|
}
|
||||||
|
ASSERT_CHAR(',');
|
||||||
|
|
||||||
|
ASSERT_FORMATTED_STR("\"%s_time\"", *type);
|
||||||
|
ASSERT_CHAR(':');
|
||||||
|
while (isdigit(*start)) {
|
||||||
|
++start;
|
||||||
|
}
|
||||||
|
ASSERT_CHAR(',');
|
||||||
|
|
||||||
|
ASSERT_FORMATTED_STR("\"%s_trace\"", *type);
|
||||||
|
ASSERT_CHAR(':');
|
||||||
|
ASSERT_CHAR('[');
|
||||||
|
while (isdigit(*start) || *start == 'x' ||
|
||||||
|
(*start >= 'a' && *start <= 'f') ||
|
||||||
|
*start == '\"' || *start == ',') {
|
||||||
|
++start;
|
||||||
|
}
|
||||||
|
ASSERT_CHAR(']');
|
||||||
|
|
||||||
|
if (strcmp(*type, "dalloc") == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(strcmp(*type, "alloc") == 0);
|
||||||
|
if (!record->released) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_CHAR(',');
|
||||||
|
++type;
|
||||||
|
}
|
||||||
|
|
||||||
|
ASSERT_CHAR('}');
|
||||||
|
|
||||||
|
#undef ASSERT_FORMATTED_STR
|
||||||
|
#undef ASSERT_STR
|
||||||
|
#undef ASSERT_CHAR
|
||||||
|
|
||||||
|
}
|
||||||
|
assert_ptr_eq(record, records + n_records, DUMP_ERROR);
|
||||||
|
assert_ptr_eq(start, end, DUMP_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_BEGIN(test_prof_recent_alloc_dump) {
|
||||||
|
test_skip_if(!config_prof);
|
||||||
|
|
||||||
|
tsd_t *tsd = tsd_fetch();
|
||||||
|
confirm_prof_setup(tsd);
|
||||||
|
|
||||||
|
ssize_t future;
|
||||||
|
void *p, *q;
|
||||||
|
confirm_record_t records[2];
|
||||||
|
|
||||||
|
future = 0;
|
||||||
|
assert_d_eq(mallctl("experimental.prof_recent.alloc_max",
|
||||||
|
NULL, NULL, &future, sizeof(ssize_t)), 0, "Write error");
|
||||||
|
call_dump();
|
||||||
|
assert_str_eq(dump_out, "{\"recent_alloc_max\":0,\"recent_alloc\":[]}",
|
||||||
|
DUMP_ERROR);
|
||||||
|
|
||||||
|
future = 2;
|
||||||
|
assert_d_eq(mallctl("experimental.prof_recent.alloc_max",
|
||||||
|
NULL, NULL, &future, sizeof(ssize_t)), 0, "Write error");
|
||||||
|
call_dump();
|
||||||
|
const char *template = "{\"recent_alloc_max\":2,\"recent_alloc\":[]}";
|
||||||
|
assert_str_eq(dump_out, template, DUMP_ERROR);
|
||||||
|
|
||||||
|
p = malloc(7);
|
||||||
|
call_dump();
|
||||||
|
records[0].size = 7;
|
||||||
|
records[0].released = false;
|
||||||
|
confirm_record(template, records, 1);
|
||||||
|
|
||||||
|
q = malloc(17);
|
||||||
|
call_dump();
|
||||||
|
records[1].size = 17;
|
||||||
|
records[1].released = false;
|
||||||
|
confirm_record(template, records, 2);
|
||||||
|
|
||||||
|
free(q);
|
||||||
|
call_dump();
|
||||||
|
records[1].released = true;
|
||||||
|
confirm_record(template, records, 2);
|
||||||
|
|
||||||
|
free(p);
|
||||||
|
call_dump();
|
||||||
|
records[0].released = true;
|
||||||
|
confirm_record(template, records, 2);
|
||||||
|
|
||||||
|
future = OPT_ALLOC_MAX;
|
||||||
|
assert_d_eq(mallctl("experimental.prof_recent.alloc_max",
|
||||||
|
NULL, NULL, &future, sizeof(ssize_t)), 0, "Write error");
|
||||||
|
confirm_prof_setup(tsd);
|
||||||
|
}
|
||||||
|
TEST_END
|
||||||
|
|
||||||
|
#undef DUMP_ERROR
|
||||||
|
#undef DUMP_OUT_SIZE
|
||||||
|
|
||||||
#define N_THREADS 16
|
#define N_THREADS 16
|
||||||
#define N_PTRS 512
|
#define N_PTRS 512
|
||||||
#define N_CTLS 8
|
#define N_CTLS 8
|
||||||
@ -500,5 +691,6 @@ main(void) {
|
|||||||
test_prof_recent_off,
|
test_prof_recent_off,
|
||||||
test_prof_recent_on,
|
test_prof_recent_on,
|
||||||
test_prof_recent_alloc,
|
test_prof_recent_alloc,
|
||||||
|
test_prof_recent_alloc_dump,
|
||||||
test_prof_recent_stress);
|
test_prof_recent_stress);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user