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 ***