Expose psset state stats

When evaluating changes in HPA logic, it is useful to know internal
`hpa_shard` state. Great deal of this state is `psset`. Some of the
`psset` stats was available, but in disaggregated form, which is not
very convenient. This commit exposed `psset` counters to `mallctl`
and malloc stats dumps.

Example of how malloc stats dump will look like after the change.

HPA shard stats:
  Pageslabs: 14899 (4354 huge, 10545 nonhuge)
  Active pages: 6708166 (2228917 huge, 4479249 nonhuge)
  Dirty pages: 233816 (331 huge, 233485 nonhuge)
  Retained pages: 686306
  Purge passes: 8730 (10 / sec)
  Purges: 127501 (146 / sec)
  Hugeifies: 4358 (5 / sec)
  Dehugifies: 4 (0 / sec)

Pageslabs, active pages, dirty pages and retained pages are rows added
by this change.
This commit is contained in:
Dmitry Ilvokhin 2024-11-14 10:52:50 -08:00
parent 3820e38dc1
commit 9a69bb6c6b
6 changed files with 458 additions and 58 deletions

View file

@ -1002,6 +1002,63 @@ TEST_BEGIN(test_stats_arenas) {
}
TEST_END
TEST_BEGIN(test_stats_arenas_hpa_shard_counters) {
test_skip_if(!config_stats);
#define TEST_STATS_ARENAS_HPA_SHARD_COUNTERS(t, name) do { \
t name; \
size_t sz = sizeof(t); \
expect_d_eq(mallctl("stats.arenas.0.hpa_shard."#name, \
(void *)&name, &sz, \
NULL, 0), 0, "Unexpected mallctl() failure"); \
} while (0)
TEST_STATS_ARENAS_HPA_SHARD_COUNTERS(size_t, npageslabs);
TEST_STATS_ARENAS_HPA_SHARD_COUNTERS(size_t, nactive);
TEST_STATS_ARENAS_HPA_SHARD_COUNTERS(size_t, ndirty);
TEST_STATS_ARENAS_HPA_SHARD_COUNTERS(uint64_t, npurge_passes);
TEST_STATS_ARENAS_HPA_SHARD_COUNTERS(uint64_t, npurges);
TEST_STATS_ARENAS_HPA_SHARD_COUNTERS(uint64_t, nhugifies);
TEST_STATS_ARENAS_HPA_SHARD_COUNTERS(uint64_t, ndehugifies);
#undef TEST_STATS_ARENAS_HPA_SHARD_COUNTERS
}
TEST_END
TEST_BEGIN(test_stats_arenas_hpa_shard_slabs) {
test_skip_if(!config_stats);
#define TEST_STATS_ARENAS_HPA_SHARD_SLABS_GEN(t, slab, name) do { \
t slab##_##name; \
size_t sz = sizeof(t); \
expect_d_eq(mallctl("stats.arenas.0.hpa_shard."#slab"."#name, \
(void *)&slab##_##name, &sz, \
NULL, 0), 0, "Unexpected mallctl() failure"); \
} while (0)
#define TEST_STATS_ARENAS_HPA_SHARD_SLABS(t, slab, name) do { \
TEST_STATS_ARENAS_HPA_SHARD_SLABS_GEN(t, slab, \
name##_##nonhuge); \
TEST_STATS_ARENAS_HPA_SHARD_SLABS_GEN(t, slab, name##_##huge); \
} while (0)
TEST_STATS_ARENAS_HPA_SHARD_SLABS(size_t, slabs, npageslabs);
TEST_STATS_ARENAS_HPA_SHARD_SLABS(size_t, slabs, nactive);
TEST_STATS_ARENAS_HPA_SHARD_SLABS(size_t, slabs, ndirty);
TEST_STATS_ARENAS_HPA_SHARD_SLABS(size_t, full_slabs, npageslabs);
TEST_STATS_ARENAS_HPA_SHARD_SLABS(size_t, full_slabs, nactive);
TEST_STATS_ARENAS_HPA_SHARD_SLABS(size_t, full_slabs, ndirty);
TEST_STATS_ARENAS_HPA_SHARD_SLABS(size_t, empty_slabs, npageslabs);
TEST_STATS_ARENAS_HPA_SHARD_SLABS(size_t, empty_slabs, nactive);
TEST_STATS_ARENAS_HPA_SHARD_SLABS(size_t, empty_slabs, ndirty);
#undef TEST_STATS_ARENAS_HPA_SHARD_SLABS
#undef TEST_STATS_ARENAS_HPA_SHARD_SLABS_GEN
}
TEST_END
static void
alloc_hook(void *extra, UNUSED hook_alloc_t type, UNUSED void *result,
UNUSED uintptr_t result_raw, UNUSED uintptr_t args_raw[3]) {
@ -1321,6 +1378,8 @@ main(void) {
test_arenas_lookup,
test_prof_active,
test_stats_arenas,
test_stats_arenas_hpa_shard_counters,
test_stats_arenas_hpa_shard_slabs,
test_hooks,
test_hooks_exhaustion,
test_thread_idle,

View file

@ -64,6 +64,24 @@ test_psset_alloc_reuse(psset_t *psset, edata_t *r_edata, size_t size) {
return false;
}
static hpdata_t *
test_psset_hugify(psset_t *psset, edata_t *edata) {
hpdata_t *ps = edata_ps_get(edata);
psset_update_begin(psset, ps);
hpdata_hugify(ps);
psset_update_end(psset, ps);
return ps;
}
static hpdata_t *
test_psset_dehugify(psset_t *psset, edata_t *edata) {
hpdata_t *ps = edata_ps_get(edata);
psset_update_begin(psset, ps);
hpdata_dehugify(ps);
psset_update_end(psset, ps);
return ps;
}
static hpdata_t *
test_psset_dalloc(psset_t *psset, edata_t *edata) {
hpdata_t *ps = edata_ps_get(edata);
@ -339,6 +357,149 @@ TEST_BEGIN(test_multi_pageslab) {
}
TEST_END
TEST_BEGIN(test_stats_merged) {
hpdata_t pageslab;
hpdata_init(&pageslab, PAGESLAB_ADDR, PAGESLAB_AGE);
edata_t alloc[HUGEPAGE_PAGES];
psset_t psset;
psset_init(&psset);
expect_zu_eq(0, psset.stats.merged.npageslabs, "");
expect_zu_eq(0, psset.stats.merged.nactive, "");
expect_zu_eq(0, psset.stats.merged.ndirty, "");
edata_init_test(&alloc[0]);
test_psset_alloc_new(&psset, &pageslab, &alloc[0], PAGE);
for (size_t i = 1; i < HUGEPAGE_PAGES; i++) {
expect_zu_eq(1, psset.stats.merged.npageslabs, "");
expect_zu_eq(i, psset.stats.merged.nactive, "");
expect_zu_eq(0, psset.stats.merged.ndirty, "");
edata_init_test(&alloc[i]);
bool err = test_psset_alloc_reuse(&psset, &alloc[i], PAGE);
expect_false(err, "Nonempty psset failed page allocation.");
}
expect_zu_eq(1, psset.stats.merged.npageslabs, "");
expect_zu_eq(HUGEPAGE_PAGES, psset.stats.merged.nactive, "");
expect_zu_eq(0, psset.stats.merged.ndirty, "");
for (ssize_t i = HUGEPAGE_PAGES - 1; i > 0; i--) {
test_psset_dalloc(&psset, &alloc[i]);
expect_zu_eq(1, psset.stats.merged.npageslabs, "");
expect_zu_eq(i, psset.stats.merged.nactive, "");
expect_zu_eq(HUGEPAGE_PAGES - i, psset.stats.merged.ndirty, "");
}
/* No allocations have left. */
test_psset_dalloc(&psset, &alloc[0]);
expect_zu_eq(0, psset.stats.merged.npageslabs, "");
expect_zu_eq(0, psset.stats.merged.nactive, "");
/*
* Last test_psset_dalloc call removed empty pageslab from psset, so
* nothing has left there, even no dirty pages.
*/
expect_zu_eq(0, psset.stats.merged.ndirty, "");
test_psset_alloc_new(&psset, &pageslab, &alloc[0], PAGE);
expect_zu_eq(1, psset.stats.merged.npageslabs, "");
expect_zu_eq(1, psset.stats.merged.nactive, "");
expect_zu_eq(0, psset.stats.merged.ndirty, "");
psset_update_begin(&psset, &pageslab);
expect_zu_eq(0, psset.stats.merged.npageslabs, "");
expect_zu_eq(0, psset.stats.merged.nactive, "");
expect_zu_eq(0, psset.stats.merged.ndirty, "");
psset_update_end(&psset, &pageslab);
expect_zu_eq(1, psset.stats.merged.npageslabs, "");
expect_zu_eq(1, psset.stats.merged.nactive, "");
expect_zu_eq(0, psset.stats.merged.ndirty, "");
}
TEST_END
TEST_BEGIN(test_stats_huge) {
test_skip_if(!config_stats);
hpdata_t pageslab;
hpdata_init(&pageslab, PAGESLAB_ADDR, PAGESLAB_AGE);
edata_t alloc[HUGEPAGE_PAGES];
psset_t psset;
psset_init(&psset);
for (int huge = 0; huge < PSSET_NHUGE; ++huge) {
expect_zu_eq(0, psset.stats.slabs[huge].npageslabs, "");
expect_zu_eq(0, psset.stats.slabs[huge].nactive, "");
expect_zu_eq(0, psset.stats.slabs[huge].ndirty, "");
}
edata_init_test(&alloc[0]);
test_psset_alloc_new(&psset, &pageslab, &alloc[0], PAGE);
for (size_t i = 1; i < HUGEPAGE_PAGES; i++) {
expect_zu_eq(1, psset.stats.slabs[0].npageslabs, "");
expect_zu_eq(i, psset.stats.slabs[0].nactive, "");
expect_zu_eq(0, psset.stats.slabs[0].ndirty, "");
expect_zu_eq(0, psset.stats.slabs[1].npageslabs, "");
expect_zu_eq(0, psset.stats.slabs[1].nactive, "");
expect_zu_eq(0, psset.stats.slabs[1].ndirty, "");
edata_init_test(&alloc[i]);
bool err = test_psset_alloc_reuse(&psset, &alloc[i], PAGE);
expect_false(err, "Nonempty psset failed page allocation.");
}
expect_zu_eq(1, psset.stats.slabs[0].npageslabs, "");
expect_zu_eq(HUGEPAGE_PAGES, psset.stats.slabs[0].nactive, "");
expect_zu_eq(0, psset.stats.slabs[0].ndirty, "");
expect_zu_eq(0, psset.stats.slabs[1].npageslabs, "");
expect_zu_eq(0, psset.stats.slabs[1].nactive, "");
expect_zu_eq(0, psset.stats.slabs[1].ndirty, "");
test_psset_hugify(&psset, &alloc[0]);
/* All stats should been moved from nonhuge to huge. */
expect_zu_eq(0, psset.stats.slabs[0].npageslabs, "");
expect_zu_eq(0, psset.stats.slabs[0].nactive, "");
expect_zu_eq(0, psset.stats.slabs[0].ndirty, "");
expect_zu_eq(1, psset.stats.slabs[1].npageslabs, "");
expect_zu_eq(HUGEPAGE_PAGES, psset.stats.slabs[1].nactive, "");
expect_zu_eq(0, psset.stats.slabs[1].ndirty, "");
test_psset_dehugify(&psset, &alloc[0]);
/* And back from huge to nonhuge after dehugification. */
expect_zu_eq(1, psset.stats.slabs[0].npageslabs, "");
expect_zu_eq(HUGEPAGE_PAGES, psset.stats.slabs[0].nactive, "");
expect_zu_eq(0, psset.stats.slabs[0].ndirty, "");
expect_zu_eq(0, psset.stats.slabs[1].npageslabs, "");
expect_zu_eq(0, psset.stats.slabs[1].nactive, "");
expect_zu_eq(0, psset.stats.slabs[1].ndirty, "");
for (ssize_t i = HUGEPAGE_PAGES - 1; i > 0; i--) {
test_psset_dalloc(&psset, &alloc[i]);
expect_zu_eq(1, psset.stats.slabs[0].npageslabs, "");
expect_zu_eq(i, psset.stats.slabs[0].nactive, "");
expect_zu_eq(HUGEPAGE_PAGES - i, psset.stats.slabs[0].ndirty, "");
expect_zu_eq(0, psset.stats.slabs[1].npageslabs, "");
expect_zu_eq(0, psset.stats.slabs[1].nactive, "");
expect_zu_eq(0, psset.stats.slabs[1].ndirty, "");
}
test_psset_dalloc(&psset, &alloc[0]);
for (int huge = 0; huge < PSSET_NHUGE; huge++) {
expect_zu_eq(0, psset.stats.slabs[huge].npageslabs, "");
expect_zu_eq(0, psset.stats.slabs[huge].nactive, "");
expect_zu_eq(0, psset.stats.slabs[huge].ndirty, "");
}
}
TEST_END
static void
stats_expect_empty(psset_bin_stats_t *stats) {
assert_zu_eq(0, stats->npageslabs,
@ -379,7 +540,9 @@ stats_expect(psset_t *psset, size_t nactive) {
expect_zu_eq(nactive, psset_nactive(psset), "");
}
TEST_BEGIN(test_stats) {
TEST_BEGIN(test_stats_fullness) {
test_skip_if(!config_stats);
bool err;
hpdata_t pageslab;
@ -739,7 +902,9 @@ main(void) {
test_reuse,
test_evict,
test_multi_pageslab,
test_stats,
test_stats_merged,
test_stats_huge,
test_stats_fullness,
test_oldest_fit,
test_insert_remove,
test_purge_prefers_nonhuge,