| #include "test/jemalloc_test.h" |
| |
| #include "jemalloc/internal/prof_recent.h" |
| |
| /* As specified in the shell script */ |
| #define OPT_ALLOC_MAX 3 |
| |
| /* Invariant before and after every test (when config_prof is on) */ |
| static void |
| confirm_prof_setup() { |
| /* Options */ |
| assert_true(opt_prof, "opt_prof not on"); |
| assert_true(opt_prof_active, "opt_prof_active not on"); |
| assert_zd_eq(opt_prof_recent_alloc_max, OPT_ALLOC_MAX, |
| "opt_prof_recent_alloc_max not set correctly"); |
| |
| /* Dynamics */ |
| assert_true(prof_active, "prof_active not on"); |
| assert_zd_eq(prof_recent_alloc_max_ctl_read(), OPT_ALLOC_MAX, |
| "prof_recent_alloc_max not set correctly"); |
| } |
| |
| TEST_BEGIN(test_confirm_setup) { |
| test_skip_if(!config_prof); |
| confirm_prof_setup(); |
| } |
| TEST_END |
| |
| TEST_BEGIN(test_prof_recent_off) { |
| test_skip_if(config_prof); |
| |
| const ssize_t past_ref = 0, future_ref = 0; |
| const size_t len_ref = sizeof(ssize_t); |
| |
| ssize_t past = past_ref, future = future_ref; |
| size_t len = len_ref; |
| |
| #define ASSERT_SHOULD_FAIL(opt, a, b, c, d) do { \ |
| assert_d_eq(mallctl("experimental.prof_recent." opt, a, b, c, \ |
| d), ENOENT, "Should return ENOENT when config_prof is off");\ |
| assert_zd_eq(past, past_ref, "output was touched"); \ |
| assert_zu_eq(len, len_ref, "output length was touched"); \ |
| assert_zd_eq(future, future_ref, "input was touched"); \ |
| } while (0) |
| |
| ASSERT_SHOULD_FAIL("alloc_max", NULL, NULL, NULL, 0); |
| ASSERT_SHOULD_FAIL("alloc_max", &past, &len, NULL, 0); |
| ASSERT_SHOULD_FAIL("alloc_max", NULL, NULL, &future, len); |
| ASSERT_SHOULD_FAIL("alloc_max", &past, &len, &future, len); |
| |
| #undef ASSERT_SHOULD_FAIL |
| } |
| TEST_END |
| |
| TEST_BEGIN(test_prof_recent_on) { |
| test_skip_if(!config_prof); |
| |
| ssize_t past, future; |
| size_t len = sizeof(ssize_t); |
| |
| confirm_prof_setup(); |
| |
| assert_d_eq(mallctl("experimental.prof_recent.alloc_max", |
| NULL, NULL, NULL, 0), 0, "no-op mallctl should be allowed"); |
| confirm_prof_setup(); |
| |
| assert_d_eq(mallctl("experimental.prof_recent.alloc_max", |
| &past, &len, NULL, 0), 0, "Read error"); |
| expect_zd_eq(past, OPT_ALLOC_MAX, "Wrong read result"); |
| future = OPT_ALLOC_MAX + 1; |
| assert_d_eq(mallctl("experimental.prof_recent.alloc_max", |
| NULL, NULL, &future, len), 0, "Write error"); |
| future = -1; |
| assert_d_eq(mallctl("experimental.prof_recent.alloc_max", |
| &past, &len, &future, len), 0, "Read/write error"); |
| expect_zd_eq(past, OPT_ALLOC_MAX + 1, "Wrong read result"); |
| future = -2; |
| assert_d_eq(mallctl("experimental.prof_recent.alloc_max", |
| &past, &len, &future, len), EINVAL, |
| "Invalid write should return EINVAL"); |
| expect_zd_eq(past, OPT_ALLOC_MAX + 1, |
| "Output should not be touched given invalid write"); |
| future = OPT_ALLOC_MAX; |
| assert_d_eq(mallctl("experimental.prof_recent.alloc_max", |
| &past, &len, &future, len), 0, "Read/write error"); |
| expect_zd_eq(past, -1, "Wrong read result"); |
| future = OPT_ALLOC_MAX + 2; |
| assert_d_eq(mallctl("experimental.prof_recent.alloc_max", |
| &past, &len, &future, len * 2), EINVAL, |
| "Invalid write should return EINVAL"); |
| expect_zd_eq(past, -1, |
| "Output should not be touched given invalid write"); |
| |
| confirm_prof_setup(); |
| } |
| TEST_END |
| |
| /* Reproducible sequence of request sizes */ |
| #define NTH_REQ_SIZE(n) ((n) * 97 + 101) |
| |
| static void |
| confirm_malloc(void *p) { |
| assert_ptr_not_null(p, "malloc failed unexpectedly"); |
| edata_t *e = emap_edata_lookup(TSDN_NULL, &arena_emap_global, p); |
| assert_ptr_not_null(e, "NULL edata for living pointer"); |
| prof_recent_t *n = edata_prof_recent_alloc_get_no_lock_test(e); |
| assert_ptr_not_null(n, "Record in edata should not be NULL"); |
| expect_ptr_not_null(n->alloc_tctx, |
| "alloc_tctx in record should not be NULL"); |
| expect_ptr_eq(e, prof_recent_alloc_edata_get_no_lock_test(n), |
| "edata pointer in record is not correct"); |
| expect_ptr_null(n->dalloc_tctx, "dalloc_tctx in record should be NULL"); |
| } |
| |
| static void |
| confirm_record_size(prof_recent_t *n, unsigned kth) { |
| expect_zu_eq(n->size, NTH_REQ_SIZE(kth), |
| "Recorded allocation size is wrong"); |
| } |
| |
| static void |
| confirm_record_living(prof_recent_t *n) { |
| expect_ptr_not_null(n->alloc_tctx, |
| "alloc_tctx in record should not be NULL"); |
| edata_t *edata = prof_recent_alloc_edata_get_no_lock_test(n); |
| assert_ptr_not_null(edata, |
| "Recorded edata should not be NULL for living pointer"); |
| expect_ptr_eq(n, edata_prof_recent_alloc_get_no_lock_test(edata), |
| "Record in edata is not correct"); |
| expect_ptr_null(n->dalloc_tctx, "dalloc_tctx in record should be NULL"); |
| } |
| |
| static void |
| confirm_record_released(prof_recent_t *n) { |
| expect_ptr_not_null(n->alloc_tctx, |
| "alloc_tctx in record should not be NULL"); |
| expect_ptr_null(prof_recent_alloc_edata_get_no_lock_test(n), |
| "Recorded edata should be NULL for released pointer"); |
| expect_ptr_not_null(n->dalloc_tctx, |
| "dalloc_tctx in record should not be NULL for released pointer"); |
| } |
| |
| TEST_BEGIN(test_prof_recent_alloc) { |
| test_skip_if(!config_prof); |
| |
| bool b; |
| unsigned i, c; |
| size_t req_size; |
| void *p; |
| prof_recent_t *n; |
| ssize_t future; |
| |
| confirm_prof_setup(); |
| |
| /* |
| * First batch of 2 * OPT_ALLOC_MAX allocations. After the |
| * (OPT_ALLOC_MAX - 1)'th allocation the recorded allocations should |
| * always be the last OPT_ALLOC_MAX allocations coming from here. |
| */ |
| for (i = 0; i < 2 * OPT_ALLOC_MAX; ++i) { |
| req_size = NTH_REQ_SIZE(i); |
| p = malloc(req_size); |
| confirm_malloc(p); |
| if (i < OPT_ALLOC_MAX - 1) { |
| assert_false(ql_empty(&prof_recent_alloc_list), |
| "Empty recent allocation"); |
| free(p); |
| /* |
| * The recorded allocations may still include some |
| * other allocations before the test run started, |
| * so keep allocating without checking anything. |
| */ |
| continue; |
| } |
| c = 0; |
| ql_foreach(n, &prof_recent_alloc_list, link) { |
| ++c; |
| confirm_record_size(n, i + c - OPT_ALLOC_MAX); |
| if (c == OPT_ALLOC_MAX) { |
| confirm_record_living(n); |
| } else { |
| confirm_record_released(n); |
| } |
| } |
| assert_u_eq(c, OPT_ALLOC_MAX, |
| "Incorrect total number of allocations"); |
| free(p); |
| } |
| |
| confirm_prof_setup(); |
| |
| b = false; |
| assert_d_eq(mallctl("prof.active", NULL, NULL, &b, sizeof(bool)), 0, |
| "mallctl for turning off prof_active failed"); |
| |
| /* |
| * Second batch of OPT_ALLOC_MAX allocations. Since prof_active is |
| * turned off, this batch shouldn't be recorded. |
| */ |
| for (; i < 3 * OPT_ALLOC_MAX; ++i) { |
| req_size = NTH_REQ_SIZE(i); |
| p = malloc(req_size); |
| assert_ptr_not_null(p, "malloc failed unexpectedly"); |
| c = 0; |
| ql_foreach(n, &prof_recent_alloc_list, link) { |
| confirm_record_size(n, c + OPT_ALLOC_MAX); |
| confirm_record_released(n); |
| ++c; |
| } |
| assert_u_eq(c, OPT_ALLOC_MAX, |
| "Incorrect total number of allocations"); |
| free(p); |
| } |
| |
| b = true; |
| assert_d_eq(mallctl("prof.active", NULL, NULL, &b, sizeof(bool)), 0, |
| "mallctl for turning on prof_active failed"); |
| |
| confirm_prof_setup(); |
| |
| /* |
| * Third batch of OPT_ALLOC_MAX allocations. Since prof_active is |
| * turned back on, they should be recorded, and in the list of recorded |
| * allocations they should follow the first batch rather than the |
| * second batch. |
| */ |
| for (; i < 4 * OPT_ALLOC_MAX; ++i) { |
| req_size = NTH_REQ_SIZE(i); |
| p = malloc(req_size); |
| confirm_malloc(p); |
| c = 0; |
| ql_foreach(n, &prof_recent_alloc_list, link) { |
| ++c; |
| confirm_record_size(n, |
| /* Is the allocation from the third batch? */ |
| i + c - OPT_ALLOC_MAX >= 3 * OPT_ALLOC_MAX ? |
| /* If yes, then it's just recorded. */ |
| i + c - OPT_ALLOC_MAX : |
| /* |
| * Otherwise, it should come from the first batch |
| * instead of the second batch. |
| */ |
| i + c - 2 * OPT_ALLOC_MAX); |
| if (c == OPT_ALLOC_MAX) { |
| confirm_record_living(n); |
| } else { |
| confirm_record_released(n); |
| } |
| } |
| assert_u_eq(c, OPT_ALLOC_MAX, |
| "Incorrect total number of allocations"); |
| free(p); |
| } |
| |
| /* Increasing the limit shouldn't alter the list of records. */ |
| future = OPT_ALLOC_MAX + 1; |
| assert_d_eq(mallctl("experimental.prof_recent.alloc_max", |
| NULL, NULL, &future, sizeof(ssize_t)), 0, "Write error"); |
| c = 0; |
| ql_foreach(n, &prof_recent_alloc_list, link) { |
| confirm_record_size(n, c + 3 * OPT_ALLOC_MAX); |
| confirm_record_released(n); |
| ++c; |
| } |
| assert_u_eq(c, OPT_ALLOC_MAX, |
| "Incorrect total number of allocations"); |
| |
| /* |
| * Decreasing the limit shouldn't alter the list of records as long as |
| * the new limit is still no less than the length of the list. |
| */ |
| future = OPT_ALLOC_MAX; |
| assert_d_eq(mallctl("experimental.prof_recent.alloc_max", |
| NULL, NULL, &future, sizeof(ssize_t)), 0, "Write error"); |
| c = 0; |
| ql_foreach(n, &prof_recent_alloc_list, link) { |
| confirm_record_size(n, c + 3 * OPT_ALLOC_MAX); |
| confirm_record_released(n); |
| ++c; |
| } |
| assert_u_eq(c, OPT_ALLOC_MAX, |
| "Incorrect total number of allocations"); |
| |
| /* |
| * Decreasing the limit should shorten the list of records if the new |
| * limit is less than the length of the list. |
| */ |
| future = OPT_ALLOC_MAX - 1; |
| assert_d_eq(mallctl("experimental.prof_recent.alloc_max", |
| NULL, NULL, &future, sizeof(ssize_t)), 0, "Write error"); |
| c = 0; |
| ql_foreach(n, &prof_recent_alloc_list, link) { |
| ++c; |
| confirm_record_size(n, c + 3 * OPT_ALLOC_MAX); |
| confirm_record_released(n); |
| } |
| assert_u_eq(c, OPT_ALLOC_MAX - 1, |
| "Incorrect total number of allocations"); |
| |
| /* Setting to unlimited shouldn't alter the list of records. */ |
| future = -1; |
| assert_d_eq(mallctl("experimental.prof_recent.alloc_max", |
| NULL, NULL, &future, sizeof(ssize_t)), 0, "Write error"); |
| c = 0; |
| ql_foreach(n, &prof_recent_alloc_list, link) { |
| ++c; |
| confirm_record_size(n, c + 3 * OPT_ALLOC_MAX); |
| confirm_record_released(n); |
| } |
| assert_u_eq(c, OPT_ALLOC_MAX - 1, |
| "Incorrect total number of allocations"); |
| |
| /* Downshift to only one record. */ |
| future = 1; |
| assert_d_eq(mallctl("experimental.prof_recent.alloc_max", |
| NULL, NULL, &future, sizeof(ssize_t)), 0, "Write error"); |
| assert_false(ql_empty(&prof_recent_alloc_list), "Recent list is empty"); |
| n = ql_first(&prof_recent_alloc_list); |
| confirm_record_size(n, 4 * OPT_ALLOC_MAX - 1); |
| confirm_record_released(n); |
| n = ql_next(&prof_recent_alloc_list, n, link); |
| assert_ptr_null(n, "Recent list should only contain one record"); |
| |
| /* Completely turn off. */ |
| future = 0; |
| assert_d_eq(mallctl("experimental.prof_recent.alloc_max", |
| NULL, NULL, &future, sizeof(ssize_t)), 0, "Write error"); |
| assert_true(ql_empty(&prof_recent_alloc_list), |
| "Recent list should be empty"); |
| |
| /* Restore the settings. */ |
| future = OPT_ALLOC_MAX; |
| assert_d_eq(mallctl("experimental.prof_recent.alloc_max", |
| NULL, NULL, &future, sizeof(ssize_t)), 0, "Write error"); |
| assert_true(ql_empty(&prof_recent_alloc_list), |
| "Recent list should be empty"); |
| |
| confirm_prof_setup(); |
| } |
| TEST_END |
| |
| #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; |
| size_t usize; |
| 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\":[]}", |
| * and dump_out would be in the form of: |
| * "{...,\"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", record->usize); |
| 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(','); |
| |
| if (opt_prof_sys_thread_name) { |
| ASSERT_FORMATTED_STR("\"%s_thread_name\"", |
| *type); |
| ASSERT_CHAR(':'); |
| ASSERT_CHAR('"'); |
| while (*start != '"') { |
| ++start; |
| } |
| ASSERT_CHAR('"'); |
| 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); |
| |
| confirm_prof_setup(); |
| |
| ssize_t future; |
| void *p, *q; |
| confirm_record_t records[2]; |
| |
| assert_zu_eq(lg_prof_sample, (size_t)0, |
| "lg_prof_sample not set correctly"); |
| |
| future = 0; |
| assert_d_eq(mallctl("experimental.prof_recent.alloc_max", |
| NULL, NULL, &future, sizeof(ssize_t)), 0, "Write error"); |
| call_dump(); |
| expect_str_eq(dump_out, "{\"sample_interval\":1," |
| "\"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 = "{\"sample_interval\":1," |
| "\"recent_alloc_max\":2,\"recent_alloc\":[]}"; |
| expect_str_eq(dump_out, template, DUMP_ERROR); |
| |
| p = malloc(7); |
| call_dump(); |
| records[0].size = 7; |
| records[0].usize = sz_s2u(7); |
| records[0].released = false; |
| confirm_record(template, records, 1); |
| |
| q = mallocx(17, MALLOCX_ALIGN(128)); |
| call_dump(); |
| records[1].size = 17; |
| records[1].usize = sz_sa2u(17, 128); |
| 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(); |
| } |
| TEST_END |
| |
| #undef DUMP_ERROR |
| #undef DUMP_OUT_SIZE |
| |
| #define N_THREADS 16 |
| #define N_PTRS 512 |
| #define N_CTLS 8 |
| #define N_ITERS 2048 |
| #define STRESS_ALLOC_MAX 4096 |
| |
| typedef struct { |
| thd_t thd; |
| size_t id; |
| void *ptrs[N_PTRS]; |
| size_t count; |
| } thd_data_t; |
| |
| static thd_data_t thd_data[N_THREADS]; |
| static ssize_t test_max; |
| |
| static void |
| test_write_cb(void *cbopaque, const char *str) { |
| sleep_ns(1000 * 1000); |
| } |
| |
| static void * |
| f_thread(void *arg) { |
| const size_t thd_id = *(size_t *)arg; |
| thd_data_t *data_p = thd_data + thd_id; |
| assert(data_p->id == thd_id); |
| data_p->count = 0; |
| uint64_t rand = (uint64_t)thd_id; |
| tsd_t *tsd = tsd_fetch(); |
| assert(test_max > 1); |
| ssize_t last_max = -1; |
| for (int i = 0; i < N_ITERS; i++) { |
| rand = prng_range_u64(&rand, N_PTRS + N_CTLS * 5); |
| assert(data_p->count <= N_PTRS); |
| if (rand < data_p->count) { |
| assert(data_p->count > 0); |
| if (rand != data_p->count - 1) { |
| assert(data_p->count > 1); |
| void *temp = data_p->ptrs[rand]; |
| data_p->ptrs[rand] = |
| data_p->ptrs[data_p->count - 1]; |
| data_p->ptrs[data_p->count - 1] = temp; |
| } |
| free(data_p->ptrs[--data_p->count]); |
| } else if (rand < N_PTRS) { |
| assert(data_p->count < N_PTRS); |
| data_p->ptrs[data_p->count++] = malloc(1); |
| } else if (rand % 5 == 0) { |
| prof_recent_alloc_dump(tsd, test_write_cb, NULL); |
| } else if (rand % 5 == 1) { |
| last_max = prof_recent_alloc_max_ctl_read(); |
| } else if (rand % 5 == 2) { |
| last_max = |
| prof_recent_alloc_max_ctl_write(tsd, test_max * 2); |
| } else if (rand % 5 == 3) { |
| last_max = |
| prof_recent_alloc_max_ctl_write(tsd, test_max); |
| } else { |
| assert(rand % 5 == 4); |
| last_max = |
| prof_recent_alloc_max_ctl_write(tsd, test_max / 2); |
| } |
| assert_zd_ge(last_max, -1, "Illegal last-N max"); |
| } |
| |
| while (data_p->count > 0) { |
| free(data_p->ptrs[--data_p->count]); |
| } |
| |
| return NULL; |
| } |
| |
| TEST_BEGIN(test_prof_recent_stress) { |
| test_skip_if(!config_prof); |
| |
| confirm_prof_setup(); |
| |
| test_max = OPT_ALLOC_MAX; |
| for (size_t i = 0; i < N_THREADS; i++) { |
| thd_data_t *data_p = thd_data + i; |
| data_p->id = i; |
| thd_create(&data_p->thd, &f_thread, &data_p->id); |
| } |
| for (size_t i = 0; i < N_THREADS; i++) { |
| thd_data_t *data_p = thd_data + i; |
| thd_join(data_p->thd, NULL); |
| } |
| |
| test_max = STRESS_ALLOC_MAX; |
| assert_d_eq(mallctl("experimental.prof_recent.alloc_max", |
| NULL, NULL, &test_max, sizeof(ssize_t)), 0, "Write error"); |
| for (size_t i = 0; i < N_THREADS; i++) { |
| thd_data_t *data_p = thd_data + i; |
| data_p->id = i; |
| thd_create(&data_p->thd, &f_thread, &data_p->id); |
| } |
| for (size_t i = 0; i < N_THREADS; i++) { |
| thd_data_t *data_p = thd_data + i; |
| thd_join(data_p->thd, NULL); |
| } |
| |
| test_max = OPT_ALLOC_MAX; |
| assert_d_eq(mallctl("experimental.prof_recent.alloc_max", |
| NULL, NULL, &test_max, sizeof(ssize_t)), 0, "Write error"); |
| confirm_prof_setup(); |
| } |
| TEST_END |
| |
| #undef STRESS_ALLOC_MAX |
| #undef N_ITERS |
| #undef N_PTRS |
| #undef N_THREADS |
| |
| int |
| main(void) { |
| return test( |
| test_confirm_setup, |
| test_prof_recent_off, |
| test_prof_recent_on, |
| test_prof_recent_alloc, |
| test_prof_recent_alloc_dump, |
| test_prof_recent_stress); |
| } |