diff --git a/src/box/memtx_allocator.cc b/src/box/memtx_allocator.cc index feb0c8a1dc13d398ca46a91bc7d7acebf2291271..81b401009d614df559390b526cb40b86b305e3de 100644 --- a/src/box/memtx_allocator.cc +++ b/src/box/memtx_allocator.cc @@ -63,6 +63,7 @@ memtx_tuple_rv_new(uint32_t version, struct rlist *list) /* List must be sorted by read view version. */ assert(l->version > prev_version); stailq_create(&l->tuples); + l->mem_used = 0; prev_version = l->version; l++; } @@ -72,6 +73,7 @@ memtx_tuple_rv_new(uint32_t version, struct rlist *list) assert(l->version > prev_version); (void)prev_version; stailq_create(&l->tuples); + l->mem_used = 0; rlist_add_tail_entry(list, new_rv, link); new_rv->refs = 1; return new_rv; @@ -79,8 +81,9 @@ memtx_tuple_rv_new(uint32_t version, struct rlist *list) void memtx_tuple_rv_delete(struct memtx_tuple_rv *rv, struct rlist *list, - struct stailq *tuples_to_free) + struct stailq *tuples_to_free, size_t *mem_freed) { + *mem_freed = 0; assert(rv->refs > 0); if (--rv->refs > 0) return; @@ -115,6 +118,7 @@ memtx_tuple_rv_delete(struct memtx_tuple_rv *rv, struct rlist *list, dst = &prev_rv->lists[j]; } stailq_concat(&dst->tuples, &src->tuples); + dst->mem_used += src->mem_used; j++; } else { /* @@ -123,6 +127,7 @@ memtx_tuple_rv_delete(struct memtx_tuple_rv *rv, struct rlist *list, * was opened. Free them immediately. */ stailq_concat(tuples_to_free, &src->tuples); + *mem_freed += src->mem_used; } i++; } @@ -131,7 +136,8 @@ memtx_tuple_rv_delete(struct memtx_tuple_rv *rv, struct rlist *list, } void -memtx_tuple_rv_add(struct memtx_tuple_rv *rv, struct memtx_tuple *tuple) +memtx_tuple_rv_add(struct memtx_tuple_rv *rv, struct memtx_tuple *tuple, + size_t mem_used) { /* * Binary search the list with min version such that @@ -152,6 +158,7 @@ memtx_tuple_rv_add(struct memtx_tuple_rv *rv, struct memtx_tuple *tuple) } assert(found != nullptr); stailq_add_entry(&found->tuples, tuple, in_gc); + found->mem_used += mem_used; } void @@ -209,3 +216,20 @@ memtx_allocators_close_read_view(memtx_allocators_read_view rv) foreach_memtx_allocator<memtx_allocator_close_read_view, memtx_allocators_read_view &>(rv); } + +/** Sums allocator statistics. */ +struct memtx_allocator_add_stats { + template<typename Allocator> + void invoke(struct memtx_allocator_stats &stats) + { + memtx_allocator_stats_add(&stats, &Allocator::stats); + } +}; + +void +memtx_allocators_stats(struct memtx_allocator_stats *stats) +{ + memtx_allocator_stats_create(stats); + foreach_memtx_allocator<memtx_allocator_add_stats, + struct memtx_allocator_stats &>(*stats); +} diff --git a/src/box/memtx_allocator.h b/src/box/memtx_allocator.h index 464f10682427763daa89a1989fdeeeecf1e2ada4..44b1e8cc3b942a8e69e2405b4ef68e809628bfc2 100644 --- a/src/box/memtx_allocator.h +++ b/src/box/memtx_allocator.h @@ -70,6 +70,8 @@ struct PACKED memtx_tuple { struct memtx_tuple_rv_list { /** Read view version. */ uint32_t version; + /** Total size of memory allocated for tuples stored in this list. */ + size_t mem_used; /** List of tuples, linked by memtx_tuple::in_gc. */ struct stailq tuples; }; @@ -157,11 +159,12 @@ memtx_tuple_rv_new(uint32_t version, struct rlist *list); /** * Deletes a list array. Tuples that are still visible from other read views * are moved to the older read view's lists. Tuples that are not visible from - * any read view are appended to the tuples_to_free list. + * any read view are appended to the tuples_to_free list. Size of memory that + * can be freed is stored in mem_freed. */ void memtx_tuple_rv_delete(struct memtx_tuple_rv *rv, struct rlist *list, - struct stailq *tuples_to_free); + struct stailq *tuples_to_free, size_t *mem_freed); /** * Adds a freed tuple to a read view's list and returns true. @@ -170,7 +173,36 @@ memtx_tuple_rv_delete(struct memtx_tuple_rv *rv, struct rlist *list, * must be < than the most recent open read view. */ void -memtx_tuple_rv_add(struct memtx_tuple_rv *rv, struct memtx_tuple *tuple); +memtx_tuple_rv_add(struct memtx_tuple_rv *rv, struct memtx_tuple *tuple, + size_t mem_used); + +/** MemtxAllocator statistics. */ +struct memtx_allocator_stats { + /** Total size of allocated memory. */ + size_t used_total; + /** Size of memory held for read views. */ + size_t used_rv; + /** Size of memory freed on demand. */ + size_t used_gc; +}; + +static inline void +memtx_allocator_stats_create(struct memtx_allocator_stats *stats) +{ + stats->used_total = 0; + stats->used_rv = 0; + stats->used_gc = 0; +} + +/* Adds memory allocator statistics from src to dst. */ +static inline void +memtx_allocator_stats_add(struct memtx_allocator_stats *dst, + const struct memtx_allocator_stats *src) +{ + dst->used_total += src->used_total; + dst->used_rv += src->used_rv; + dst->used_gc += src->used_gc; +} template<class Allocator> class MemtxAllocator { @@ -186,8 +218,12 @@ class MemtxAllocator { struct memtx_tuple_rv *rv[memtx_tuple_rv_type_MAX]; }; + /** Memory usage statistics. */ + static struct memtx_allocator_stats stats; + static void create() { + memtx_allocator_stats_create(&stats); stailq_create(&gc); for (int type = 0; type < memtx_tuple_rv_type_MAX; type++) rlist_create(&read_views[type]); @@ -238,8 +274,12 @@ class MemtxAllocator { for (int type = 0; type < memtx_tuple_rv_type_MAX; type++) { if (rv->rv[type] == nullptr) continue; - memtx_tuple_rv_delete(rv->rv[type], - &read_views[type], &gc); + size_t mem_freed = 0; + memtx_tuple_rv_delete(rv->rv[type], &read_views[type], + &gc, &mem_freed); + assert(stats.used_rv >= mem_freed); + stats.used_rv -= mem_freed; + stats.used_gc += mem_freed; } TRASH(rv); ::free(rv); @@ -277,14 +317,17 @@ class MemtxAllocator { */ static void free_tuple(struct tuple *tuple) { + size_t size = tuple_size(tuple) + + offsetof(struct memtx_tuple, base); struct memtx_tuple *memtx_tuple = container_of( tuple, struct memtx_tuple, base); struct memtx_tuple_rv *rv = tuple_rv_last(tuple); if (rv == nullptr || memtx_tuple->version >= memtx_tuple_rv_version(rv)) { - immediate_free_tuple(memtx_tuple); + free(memtx_tuple, size); } else { - memtx_tuple_rv_add(rv, memtx_tuple); + stats.used_rv += size; + memtx_tuple_rv_add(rv, memtx_tuple, size); } } @@ -297,7 +340,11 @@ class MemtxAllocator { for (int i = 0; !stailq_empty(&gc) && i < GC_BATCH_SIZE; i++) { struct memtx_tuple *memtx_tuple = stailq_shift_entry( &gc, struct memtx_tuple, in_gc); - immediate_free_tuple(memtx_tuple); + size_t size = tuple_size(&memtx_tuple->base) + + offsetof(struct memtx_tuple, base); + assert(stats.used_gc >= size); + stats.used_gc -= size; + free(memtx_tuple, size); } return !stailq_empty(&gc); } @@ -307,20 +354,18 @@ class MemtxAllocator { static void free(void *ptr, size_t size) { + assert(stats.used_total >= size); + stats.used_total -= size; Allocator::free(ptr, size); } static void *alloc(size_t size) { collect_garbage(); - return Allocator::alloc(size); - } - - static void immediate_free_tuple(struct memtx_tuple *memtx_tuple) - { - size_t size = tuple_size(&memtx_tuple->base) + - offsetof(struct memtx_tuple, base); - free(memtx_tuple, size); + void *ptr = Allocator::alloc(size); + if (ptr != NULL) + stats.used_total += size; + return ptr; } /** @@ -407,6 +452,9 @@ double MemtxAllocator<Allocator>::read_view_timestamp; template<class Allocator> bool MemtxAllocator<Allocator>::may_reuse_read_view; +template<class Allocator> +struct memtx_allocator_stats MemtxAllocator<Allocator>::stats; + void memtx_allocators_init(struct allocator_settings *settings); @@ -428,6 +476,10 @@ memtx_allocators_open_read_view(const struct read_view_opts *opts); void memtx_allocators_close_read_view(memtx_allocators_read_view rv); +/** Returns allocator statistics sum over all MemtxAllocators. */ +void +memtx_allocators_stats(struct memtx_allocator_stats *stats); + template<class F, class...Arg> static void foreach_memtx_allocator(Arg&&...arg) diff --git a/test/unit/memtx_allocator.cc b/test/unit/memtx_allocator.cc index d43852983433151a5b5d3c3a45edd264a0d68b56..2cd830d21b054e9c686fb43ffa4a406088715c50 100644 --- a/test/unit/memtx_allocator.cc +++ b/test/unit/memtx_allocator.cc @@ -12,6 +12,8 @@ #include "small/slab_cache.h" #include "small/quota.h" #include "trivia/util.h" + +#define UNIT_TAP_COMPATIBLE 1 #include "unit.h" #define ARENA_SIZE (16 * 1024 * 1024) @@ -477,10 +479,80 @@ test_reuse_read_view() check_plan(); } +static void +test_mem_used() +{ + plan(21); + header(); + + struct memtx_allocator_stats stats; + memtx_allocators_stats(&stats); + is(stats.used_total, 0, "used_total init"); + is(stats.used_rv, 0, "used_rv init"); + is(stats.used_gc, 0, "used_gc init"); + + size_t tuple_size = sizeof(struct tuple) + + offsetof(struct memtx_tuple, base); + struct tuple *tuple = alloc_tuple(); + + struct tuple *tuple1 = alloc_tuple(); + struct read_view_opts opts; + read_view_opts_create(&opts); + memtx_allocators_read_view rv1 = memtx_allocators_open_read_view(&opts); + free_tuple(tuple); + struct tuple *tuple2 = alloc_tuple(); + memtx_allocators_read_view rv2 = memtx_allocators_open_read_view(&opts); + + memtx_allocators_stats(&stats); + is(stats.used_total, 3 * tuple_size, + "used_total after opening read views"); + is(stats.used_rv, tuple_size, "used_rv after opening read views"); + is(stats.used_gc, 0, "used_gc after opening read views"); + + free_tuple(tuple1); + + memtx_allocators_stats(&stats); + is(stats.used_total, 3 * tuple_size, "used_total after freeing tuple1"); + is(stats.used_rv, 2 * tuple_size, "used_rv after freeing tuple1"); + is(stats.used_gc, 0, "used_gc after freeing tuple1"); + + free_tuple(tuple2); + + memtx_allocators_stats(&stats); + is(stats.used_total, 3 * tuple_size, "used_total after freeing tuple2"); + is(stats.used_rv, 3 * tuple_size, "used_rv after freeing tuple2"); + is(stats.used_gc, 0, "used_gc after freeing tuple2"); + + memtx_allocators_close_read_view(rv1); + + memtx_allocators_stats(&stats); + is(stats.used_total, 3 * tuple_size, "used_total after closing rv1"); + is(stats.used_rv, 2 * tuple_size, "used_rv after closing rv1"); + is(stats.used_gc, tuple_size, "used_gc after closing rv1"); + + memtx_allocators_close_read_view(rv2); + + memtx_allocators_stats(&stats); + is(stats.used_total, 3 * tuple_size, "used_total after closing rv2"); + is(stats.used_rv, 0, "used_rv after closing rv2"); + is(stats.used_gc, 3 * tuple_size, "used_gc after closing rv2"); + + while (MemtxAllocator<SmallAlloc>::collect_garbage()) { + } + + memtx_allocators_stats(&stats); + is(stats.used_total, 0, "used_total after gc"); + is(stats.used_rv, 0, "used_rv after gc"); + is(stats.used_gc, 0, "used_gc after gc"); + + footer(); + check_plan(); +} + static int test_main() { - plan(8); + plan(9); header(); test_alloc_stats(); @@ -491,6 +563,7 @@ test_main() test_tuple_gc(); test_temp_tuple_gc(); test_reuse_read_view(); + test_mem_used(); footer(); return check_plan(); diff --git a/test/unit/memtx_allocator.result b/test/unit/memtx_allocator.result deleted file mode 100644 index 294ddf2226a314a88ee9f7f528ef5f86b7b1e41e..0000000000000000000000000000000000000000 --- a/test/unit/memtx_allocator.result +++ /dev/null @@ -1,92 +0,0 @@ -1..8 - *** test_main *** - 1..5 - *** test_alloc_stats *** - ok 1 - count before alloc - ok 2 - count after alloc 1 - ok 3 - count after alloc 2 - ok 4 - count after free 1 - ok 5 - count after free 2 - *** test_alloc_stats: done *** -ok 1 - subtests - 1..4 - *** test_free_delayed_if_alloc_before_read_view *** - ok 1 - count before alloc - ok 2 - count after alloc - ok 3 - count after free - ok 4 - count after read view closed - *** test_free_delayed_if_alloc_before_read_view: done *** -ok 2 - subtests - 1..5 - *** test_free_delayed_until_all_read_views_closed *** - ok 1 - count before alloc - ok 2 - count after alloc - ok 3 - count after free - ok 4 - count after first read view closed - ok 5 - count after second read view closed - *** test_free_delayed_until_all_read_views_closed: done *** -ok 3 - subtests - 1..3 - *** test_free_not_delayed_if_alloc_after_read_view *** - ok 1 - count before alloc - ok 2 - count after alloc - ok 3 - count after free - *** test_free_not_delayed_if_alloc_after_read_view: done *** -ok 4 - subtests - 1..3 - *** test_free_not_delayed_if_temporary *** - ok 1 - count before alloc - ok 2 - count after alloc - ok 3 - count after free - *** test_free_not_delayed_if_temporary: done *** -ok 5 - subtests - 1..11 - *** test_tuple_gc *** - ok 1 - count before alloc - ok 2 - count after rv1 opened - ok 3 - count after rv2 opened - ok 4 - count after rv3 opened - ok 5 - count before rv2 closed - ok 6 - count after rv2 closed - ok 7 - count after rv4 opened - ok 8 - count before rv4 closed - ok 9 - count after rv4 closed - ok 10 - count after rv1 closed - ok 11 - count after rv3 closed - *** test_tuple_gc: done *** -ok 6 - subtests - 1..10 - *** test_temp_tuple_gc *** - ok 1 - count before alloc - ok 2 - count after rv1 opened - ok 3 - count after rv2 opened - ok 4 - count after rv3 opened - ok 5 - count after rv4 opened - ok 6 - count before rv4 closed - ok 7 - count after rv4 closed - ok 8 - count after rv3 closed - ok 9 - count after rv2 closed - ok 10 - count after rv1 closed - *** test_temp_tuple_gc: done *** -ok 7 - subtests - 1..16 - *** test_reuse_read_view *** - ok 1 - count before alloc - ok 2 - count after rv1 opened - ok 3 - count after rv2 opened - ok 4 - count after rv3 opened - ok 5 - count after rv4 opened - ok 6 - count after rv5 opened - ok 7 - count after rv6 opened - ok 8 - count after rv7 opened - ok 9 - count before rv7 closed - ok 10 - count after rv7 closed - ok 11 - count after rv6 closed - ok 12 - count after rv2 closed - ok 13 - count after rv1 closed - ok 14 - count after rv3 closed - ok 15 - count after rv5 closed - ok 16 - count after rv4 closed - *** test_reuse_read_view: done *** -ok 8 - subtests - *** test_main: done ***