Commit Diff


commit - 3a3890ed9389c83a0fcf7e061b23ae947da9d4a2
commit + ab24dfb65b45ab372ccbf4831dde80d3887e686b
blob - d0669149f10e188ce871d8e6d439b7dfbe5fa713
blob + f211875151b1d763889f86cca28d7886666b881f
--- src/box/allocator.h
+++ src/box/allocator.h
@@ -129,6 +129,11 @@ class SmallAlloc (public)
 	{
 		return &small_alloc;
 	}
+	static inline void
+	get_alloc_info(void *ptr, size_t size, struct small_alloc_info *info)
+	{
+		small_alloc_info(&small_alloc, ptr, size, info);
+	}
 private:
 	static struct small_alloc small_alloc;
 };
blob - 1fed5050603e696114efa4e331832cf142644671
blob + 5eedde8721c779c92bc8745d4b896a661336b5fd
--- src/box/lua/tuple.c
+++ src/box/lua/tuple.c
@@ -720,6 +720,41 @@ luaT_pushtuple(struct lua_State *L, box_tuple_t *tuple
 	assert(luaT_tuple_gc_ref != LUA_NOREF);
 	lua_rawgeti(L, LUA_REGISTRYINDEX, luaT_tuple_gc_ref);
 	luaL_setcdatagc(L, -2);
+}
+
+/**
+ * Push to Lua stack a table with the information about a tuple, located on top
+ * of the stack.
+ */
+static int
+lbox_tuple_info(lua_State *L)
+{
+	int argc = lua_gettop(L);
+	if (argc != 1)
+		luaL_error(L, "Usage: tuple:info()");
+
+	struct tuple *tuple = luaT_checktuple(L, 1);
+	struct tuple_info info;
+	tuple_info(tuple, &info);
+
+	lua_newtable(L);
+
+	lua_pushnumber(L, info.data_size);
+	lua_setfield(L, -2, "data_size");
+
+	lua_pushnumber(L, info.header_size);
+	lua_setfield(L, -2, "header_size");
+
+	lua_pushnumber(L, info.field_map_size);
+	lua_setfield(L, -2, "field_map_size");
+
+	lua_pushnumber(L, info.waste_size);
+	lua_setfield(L, -2, "waste_size");
+
+	lua_pushstring(L, tuple_arena_type_strs[info.arena_type]);
+	lua_setfield(L, -2, "arena");
+
+	return 1;
 }
 
 static const struct luaL_Reg lbox_tuple_meta[] = {
@@ -730,6 +765,7 @@ static const struct luaL_Reg lbox_tuple_meta[] = {
 	{"tuple_to_map", lbox_tuple_to_map},
 	{"tuple_field_by_path", lbox_tuple_field_by_path},
 	{"new", lbox_tuple_new},
+	{"info", lbox_tuple_info},
 	{NULL, NULL}
 };
 
blob - 584fe32ab1a69a8b177e49bcd12cdc583f5b4066
blob + 5f63acbd650cabfb351b87ac95e14445a8c684d5
--- src/box/lua/tuple.lua
+++ src/box/lua/tuple.lua
@@ -333,6 +333,7 @@ local methods = {
     ["upsert"]      = tuple_upsert;
     ["bsize"]       = tuple_bsize;
     ["tomap"]       = internal.tuple.tuple_to_map;
+    ["info"]        = internal.tuple.info;
 }
 
 -- Aliases for tuple:methods().
blob - 7a1359778c41b7e054ca6f03fc10172f78d2472d
blob + 934da1fc806851ec1ab17eb4561d6bad230229ad
--- src/box/memtx_engine.cc
+++ src/box/memtx_engine.cc
@@ -1879,16 +1879,62 @@ memtx_tuple_delete(struct tuple_format *format, struct
 	assert(tuple_is_unreferenced(tuple));
 	MemtxAllocator<ALLOC>::free_tuple(tuple);
 	tuple_format_unref(format);
+}
+
+/** Fill `info' with the information about the `tuple'. */
+template<class ALLOC>
+static inline void
+memtx_tuple_info(struct tuple_format *format, struct tuple *tuple,
+		 struct tuple_info *info);
+
+template<>
+inline void
+memtx_tuple_info<SmallAlloc>(struct tuple_format *format, struct tuple *tuple,
+			     struct tuple_info *info)
+{
+	(void)format;
+	size_t size = tuple_size(tuple);
+	struct small_alloc_info alloc_info;
+	SmallAlloc::get_alloc_info(tuple, size, &alloc_info);
+
+	info->data_size = tuple_bsize(tuple);
+	info->header_size = sizeof(struct tuple);
+	if (tuple_is_compact(tuple))
+		info->header_size -= TUPLE_COMPACT_SAVINGS;
+	info->field_map_size = tuple_data_offset(tuple) - info->header_size;
+	if (alloc_info.is_large) {
+		info->waste_size = 0;
+		info->arena_type = TUPLE_ARENA_MALLOC;
+	} else {
+		info->waste_size = alloc_info.real_size - size;
+		info->arena_type = TUPLE_ARENA_MEMTX;
+	}
 }
 
+template<>
+inline void
+memtx_tuple_info<SysAlloc>(struct tuple_format *format, struct tuple *tuple,
+			   struct tuple_info *info)
+{
+	(void)format;
+	info->data_size = tuple_bsize(tuple);
+	info->header_size = sizeof(struct tuple);
+	if (tuple_is_compact(tuple))
+		info->header_size -= TUPLE_COMPACT_SAVINGS;
+	info->field_map_size = tuple_data_offset(tuple) - info->header_size;
+	info->waste_size = 0;
+	info->arena_type = TUPLE_ARENA_MALLOC;
+}
+
 struct tuple_format_vtab memtx_tuple_format_vtab;
 
-template <class ALLOC>
+template<class ALLOC>
 static inline void
 create_memtx_tuple_format_vtab(struct tuple_format_vtab *vtab)
 {
 	vtab->tuple_delete = memtx_tuple_delete<ALLOC>;
 	vtab->tuple_new = memtx_tuple_new<ALLOC>;
+	vtab->tuple_info = memtx_tuple_info<ALLOC>;
 }
 
 /**
blob - 30277a186c661f80df06ccb085d22e1d1f8f3c2c
blob + 70041187336339c66167fca710bed42b892416d2
--- src/box/tuple.c
+++ src/box/tuple.c
@@ -46,6 +46,12 @@ enum {
 	OBJSIZE_MIN = 16,
 };
 
+const char *tuple_arena_type_strs[tuple_arena_type_MAX] = {
+	[TUPLE_ARENA_MEMTX] = "memtx",
+	[TUPLE_ARENA_MALLOC] = "malloc",
+	[TUPLE_ARENA_RUNTIME] = "runtime",
+};
+
 /**
  * Storage for additional reference counter of a tuple.
  */
@@ -93,10 +99,16 @@ runtime_tuple_delete(struct tuple_format *format, stru
 static struct tuple *
 runtime_tuple_new(struct tuple_format *format, const char *data, const char *end);
 
+/** Fill `tuple_info'. */
+static void
+runtime_tuple_info(struct tuple_format *format, struct tuple *tuple,
+		   struct tuple_info *tuple_info);
+
 /** A virtual method table for tuple_format_runtime */
 static struct tuple_format_vtab tuple_format_runtime_vtab = {
 	runtime_tuple_delete,
 	runtime_tuple_new,
+	runtime_tuple_info,
 };
 
 static struct tuple *
@@ -150,6 +162,24 @@ runtime_tuple_delete(struct tuple_format *format, stru
 	size_t total = tuple_size(tuple);
 	tuple_format_unref(format);
 	smfree(&runtime_alloc, tuple, total);
+}
+
+static void
+runtime_tuple_info(struct tuple_format *format, struct tuple *tuple,
+		   struct tuple_info *tuple_info)
+{
+	assert(format->vtab.tuple_delete ==
+	       tuple_format_runtime_vtab.tuple_delete);
+	(void)format;
+
+	uint16_t data_offset = tuple_data_offset(tuple);
+	tuple_info->data_size = tuple_bsize(tuple);
+	tuple_info->header_size = sizeof(struct tuple);
+	if (tuple_is_compact(tuple))
+		tuple_info->header_size -= TUPLE_COMPACT_SAVINGS;
+	tuple_info->field_map_size = data_offset - tuple_info->header_size;
+	tuple_info->waste_size = 0;
+	tuple_info->arena_type = TUPLE_ARENA_RUNTIME;
 }
 
 int
blob - be32936723e40b2a244ea7588672603b9228514d
blob + f151e2b75e0f0f0d016efaedcc24d5010106d825
--- src/box/tuple.h
+++ src/box/tuple.h
@@ -458,6 +458,44 @@ struct PACKED tuple
 };
 
 static_assert(sizeof(struct tuple) == 10, "Just to be sure");
+
+/** Type of the arena where the tuple is allocated. */
+enum tuple_arena_type {
+	TUPLE_ARENA_MEMTX = 0,
+	TUPLE_ARENA_MALLOC = 1,
+	TUPLE_ARENA_RUNTIME = 2,
+	tuple_arena_type_MAX
+};
+
+/** Arena type names. */
+extern const char *tuple_arena_type_strs[tuple_arena_type_MAX];
+
+/** Information about the tuple. */
+struct tuple_info {
+	/** Size of the MsgPack data. See also tuple_bsize(). */
+	size_t data_size;
+	/** Header size depends on the engine and on the compact/bulky mode. */
+	size_t header_size;
+	/** Size of the field_map. See also field_map_build_size(). */
+	size_t field_map_size;
+	/**
+	 * The amount of excess memory used to store the tuple in mempool.
+	 * Note that this value is calculated not during the actual allocation,
+	 * but afterwards. This means that it can be incorrect if the state of
+	 * the allocator changed. See also small_alloc_info().
+	 */
+	size_t waste_size;
+	/** Type of the arena where the tuple is allocated. */
+	enum tuple_arena_type arena_type;
+};
+
+/** Fill `info' with the information about the `tuple'. */
+static inline void
+tuple_info(struct tuple *tuple, struct tuple_info *info)
+{
+	struct tuple_format *format = tuple_format_by_id(tuple->format_id);
+	format->vtab.tuple_info(format, tuple, info);
+}
 
 static_assert(DIV_ROUND_UP(tuple_flag_MAX, 8) <=
 	      sizeof(((struct tuple *)0)->flags),
blob - 8c37f36a6f3f267535f40d94a48fa4efe8c42ab6
blob + d261e06e1b39ca4a54947ede9c51f9316df11c46
--- src/box/tuple_format.h
+++ src/box/tuple_format.h
@@ -66,6 +66,7 @@ enum { TUPLE_INDEX_BASE = 1 };
 enum { TUPLE_OFFSET_SLOT_NIL = INT32_MAX };
 
 struct tuple;
+struct tuple_info;
 struct tuple_format;
 struct coll;
 struct Expr;
@@ -104,6 +105,13 @@ struct tuple_format_vtab {
 	struct tuple*
 	(*tuple_new)(struct tuple_format *format, const char *data,
 	             const char *end);
+	/**
+	 * Fill `tuple_info' with the engine-specific and allocator-specific
+	 * information about the `tuple'.
+	 */
+	void
+	(*tuple_info)(struct tuple_format *format, struct tuple *tuple,
+		      struct tuple_info *tuple_info);
 };
 
 struct tuple_constraint;
blob - b9eabbbdc1a6af1b25183ed000a85d8160d1de33
blob + 56be337bfc25016d8278b1b50313118f8703b0d3
--- src/box/vy_stmt.c
+++ src/box/vy_stmt.c
@@ -119,11 +119,26 @@ vy_tuple_delete(struct tuple_format *format, struct tu
 	free(tuple);
 }
 
+/** Fill `tuple_info'. */
+static void
+vy_tuple_info(struct tuple_format *format, struct tuple *tuple,
+	      struct tuple_info *tuple_info)
+{
+	(void)format;
+	uint16_t data_offset = tuple_data_offset(tuple);
+	tuple_info->data_size = tuple_bsize(tuple);
+	tuple_info->header_size = sizeof(struct vy_stmt);
+	tuple_info->field_map_size = data_offset - tuple_info->header_size;
+	tuple_info->waste_size = 0;
+	tuple_info->arena_type = TUPLE_ARENA_MALLOC;
+}
+
 void
 vy_stmt_env_create(struct vy_stmt_env *env)
 {
 	env->tuple_format_vtab.tuple_new = vy_tuple_new;
 	env->tuple_format_vtab.tuple_delete = vy_tuple_delete;
+	env->tuple_format_vtab.tuple_info = vy_tuple_info;
 	env->max_tuple_size = 1024 * 1024;
 	env->sum_tuple_size = 0;
 	env->key_format = vy_simple_stmt_format_new(env, NULL, 0);
blob - /dev/null
blob + 3318921b4a0dd940446e4b2726c7227f69859567 (mode 644)
--- /dev/null
+++ test/box-luatest/gh_6762_tuple_and_space_size_test.lua
@@ -0,0 +1,131 @@
+local t = require('luatest')
+local tarantool = require('tarantool')
+local server = require('luatest.server')
+
+-- If ASAN is enabled, the information about memory allocation is different.
+-- There is no sense in testing it.
+local function skip_if_asan_is_enabled()
+    t.skip_if(tarantool.build.asan)
+end
+
+local function after_all(cg)
+    cg.server:drop()
+end
+
+local function after_each(cg)
+    cg.server:exec(function()
+        if box.space.memtx then
+            box.space.memtx:drop()
+        end
+        if box.space.vinyl then
+            box.space.vinyl:drop()
+        end
+    end)
+end
+
+local g1 = t.group('gh-6762-tuple-info')
+
+g1.before_all(function(cg)
+    local box_cfg = {
+        slab_alloc_factor = 1.3,
+        slab_alloc_granularity = 32,
+        memtx_max_tuple_size = 1200*1000
+    }
+    cg.server = server:new{box_cfg = box_cfg}
+    cg.server:start()
+end)
+g1.after_all(after_all)
+g1.after_each(after_each)
+
+-- Test info() method of a runtime tuple.
+g1.test_tuple_info_runtime = function(cg)
+    cg.server:exec(function()
+        -- Compact form of a tuple.
+        t.assert_equals(box.tuple.new{string.rep('c', 252)}:info(),
+                        { data_size = 255,
+                          header_size = 6,
+                          field_map_size = 0,
+                          waste_size = 0,
+                          arena = "runtime" }
+        )
+        -- Bulky form of a tuple.
+        t.assert_equals(box.tuple.new{string.rep('b', 253)}:info(),
+                        { data_size = 256,
+                          header_size = 10,
+                          field_map_size = 0,
+                          waste_size = 0,
+                          arena = "runtime" }
+        )
+    end)
+end
+
+-- Test info() method of a memtx tuple.
+g1.test_tuple_info_memtx = function(cg)
+    skip_if_asan_is_enabled()
+    cg.server:exec(function()
+        local s = box.schema.space.create('memtx')
+        s:create_index('pk', {parts = {2}})
+
+        -- Compact form of a tuple.
+        t.assert_equals(s:insert{string.rep('c', 251), 0}:info(),
+                        { data_size = 255,
+                          header_size = 6,
+                          field_map_size = 4,
+                          waste_size = 247,
+                          arena = "memtx" }
+        )
+        -- Bulky form of a tuple.
+        t.assert_equals(s:insert{string.rep('b', 252), 1}:info(),
+                        { data_size = 256,
+                          header_size = 10,
+                          field_map_size = 4,
+                          waste_size = 242,
+                          arena = "memtx" }
+        )
+        -- malloc'ed tuple.
+        t.assert_equals(s:insert{string.rep('m', 1100*1000), 2}:info(),
+                        { data_size = 1100007,
+                          header_size = 10,
+                          field_map_size = 4,
+                          waste_size = 0,
+                          arena = "malloc" }
+        )
+        -- Check that info().data_size equals bsize()
+        t.assert_equals(s:get(0):info().data_size, s:get(0):bsize())
+        t.assert_equals(s:get(1):info().data_size, s:get(1):bsize())
+        t.assert_equals(s:get(2):info().data_size, s:get(2):bsize())
+    end)
+end
+
+-- Test info() method of a vinyl tuple.
+g1.test_tuple_info_vinyl = function(cg)
+    cg.server:exec(function()
+        local s = box.schema.space.create('vinyl', {engine = 'vinyl'})
+        s:create_index('pk', {parts = {2}})
+        t.assert_equals(s:insert{'v', 0}:info(),
+                        { data_size = 4,
+                          header_size = 24,
+                          field_map_size = 4,
+                          waste_size = 0,
+                          arena = "malloc" }
+        )
+        -- Check that info().data_size equals bsize()
+        t.assert_equals(s:get(0):info().data_size, s:get(0):bsize())
+    end)
+end
+
+-- Test error messages.
+g1.test_errors = function()
+    t.assert_error_msg_content_equals(
+        'Usage: tuple:info()',
+        function() box.tuple.new{0}.info() end
+    )
+    t.assert_error_msg_content_equals(
+        'Usage: tuple:info()',
+        function() box.tuple.new{0}:info('xxx') end
+    )
+    t.assert_error_msg_equals(
+        'Invalid argument #1 (box.tuple expected, got string)',
+        box.tuple.new{0}.info, 'xxx'
+    )
+end
blob - c5e19430353c7e8aa87217f3e9108a0f45247286
blob + eecd3371d6fb34d24ee509fab3e65c7cab571950
--- test/unit/memtx_allocator.cc
+++ test/unit/memtx_allocator.cc
@@ -48,9 +48,20 @@ test_tuple_delete(struct tuple_format *format, struct 
 	MemtxAllocator<SmallAlloc>::free_tuple(tuple);
 }
 
+static void
+test_tuple_info(struct tuple_format *format, struct tuple *tuple,
+		struct tuple_info *tuple_info)
+{
+	assert(format == test_tuple_format);
+	(void)format;
+	(void)tuple;
+	(void)tuple_info;
+}
+
 static struct tuple_format_vtab test_tuple_format_vtab = {
 	.tuple_delete = test_tuple_delete,
 	.tuple_new = test_tuple_new,
+	.tuple_info = test_tuple_info,
 };
 
 static struct tuple *