From 9da7e03e88e29b8d38ff8368fda1da4d87f6460b Mon Sep 17 00:00:00 2001
From: Iskander Sagitov <i.sagitov@tarantool.org>
Date: Thu, 13 May 2021 11:42:36 +0300
Subject: [PATCH] fiber: introduce fiber_o:info() and fiber_o:csw()

If you want to get information or get csw (Context SWitch) of some fiber
you need to call fiber.info(), but it creates table with information about
all the fibers. This patch introduces fiber_object:info() and
fiber_object:csw() - functions to solve this problem.

Closes #5799

@TarantoolBot document
Title: introduce fiber_object:info() and fiber_object:csw()
```
-- fiber_object:info() is the same as fiber.info(), but show information only
about one alive fiber.
-- fiber_object:csw() show csw (Context SWitch) of alive fiber.
```
---
 .../gh-5799-add-info-to-fiber-object.md       |  4 +
 src/lua/fiber.c                               | 99 +++++++++++++++----
 test/app/gh-5799-fiber-info.result            | 80 +++++++++++++++
 test/app/gh-5799-fiber-info.test.lua          | 37 +++++++
 4 files changed, 199 insertions(+), 21 deletions(-)
 create mode 100644 changelogs/unreleased/gh-5799-add-info-to-fiber-object.md
 create mode 100644 test/app/gh-5799-fiber-info.result
 create mode 100644 test/app/gh-5799-fiber-info.test.lua

diff --git a/changelogs/unreleased/gh-5799-add-info-to-fiber-object.md b/changelogs/unreleased/gh-5799-add-info-to-fiber-object.md
new file mode 100644
index 0000000000..3b5faad60b
--- /dev/null
+++ b/changelogs/unreleased/gh-5799-add-info-to-fiber-object.md
@@ -0,0 +1,4 @@
+## feature/lua/fiber
+
+* Introduce `fiber_object:info()` to get `info` from fiber. Works as `require(fiber).info()` but only for one fiber.
+* Introduce `fiber_object:csw()` to get `csw` from fiber (gh-5799).
diff --git a/src/lua/fiber.c b/src/lua/fiber.c
index 0800a1c269..91898c283c 100644
--- a/src/lua/fiber.c
+++ b/src/lua/fiber.c
@@ -263,13 +263,10 @@ fiber_backtrace_cb(int frameno, void *frameret, const char *func, size_t offset,
 #endif
 
 static int
-lbox_fiber_statof(struct fiber *f, void *cb_ctx, bool backtrace)
+lbox_fiber_statof_map(struct fiber *f, void *cb_ctx, bool backtrace)
 {
 	struct lua_State *L = (struct lua_State *) cb_ctx;
 
-	luaL_pushuint64(L, f->fid);
-	lua_newtable(L);
-
 	lua_pushliteral(L, "name");
 	lua_pushstring(L, fiber_name(f));
 	lua_settable(L, -3);
@@ -313,6 +310,16 @@ lbox_fiber_statof(struct fiber *f, void *cb_ctx, bool backtrace)
 		lua_settable(L, -3);
 #endif /* ENABLE_BACKTRACE */
 	}
+	return 0;
+}
+
+static int
+lbox_fiber_statof(struct fiber *f, void *cb_ctx, bool backtrace)
+{
+	struct lua_State *L = cb_ctx;
+	luaL_pushuint64(L, f->fid);
+	lua_newtable(L);
+	lbox_fiber_statof_map(f, cb_ctx, backtrace);
 	lua_settable(L, -3);
 	return 0;
 }
@@ -406,14 +413,10 @@ lbox_fiber_top_disable(struct lua_State *L)
 }
 #endif /* ENABLE_FIBER_TOP */
 
-/**
- * Return fiber statistics.
- */
-static int
-lbox_fiber_info(struct lua_State *L)
-{
 #ifdef ENABLE_BACKTRACE
-	bool do_backtrace = true;
+bool
+lbox_do_backtrace(struct lua_State *L)
+{
 	if (lua_istable(L, 1)) {
 		lua_pushstring(L, "backtrace");
 		lua_gettable(L, 1);
@@ -423,9 +426,21 @@ lbox_fiber_info(struct lua_State *L)
 			lua_gettable(L, 1);
 		}
 		if (!lua_isnil(L, -1))
-			do_backtrace = lua_toboolean(L, -1);
+			return lua_toboolean(L, -1);
 		lua_pop(L, 1);
 	}
+	return true;
+}
+#endif /* ENABLE_BACKTRACE */
+
+/**
+ * Return fiber statistics.
+ */
+static int
+lbox_fiber_info(struct lua_State *L)
+{
+#ifdef ENABLE_BACKTRACE
+	bool do_backtrace = lbox_do_backtrace(L);
 	if (do_backtrace) {
 		lua_newtable(L);
 		fiber_stat(lbox_fiber_statof_bt, L);
@@ -527,6 +542,17 @@ lbox_fiber_new(struct lua_State *L)
 	return 1;
 }
 
+static struct fiber *
+lbox_get_fiber(struct lua_State *L)
+{
+	if (lua_gettop(L) != 0) {
+		uint64_t *fid = luaL_checkudata(L, 1, fiberlib_name);
+		return fiber_find(*fid);
+	} else {
+		return fiber();
+	}
+}
+
 /**
  * Get fiber status.
  * This follows the rules of Lua coroutine.status() function:
@@ -540,16 +566,9 @@ lbox_fiber_new(struct lua_State *L)
 static int
 lbox_fiber_status(struct lua_State *L)
 {
-	struct fiber *f;
-	if (lua_gettop(L)) {
-		uint64_t fid = *(uint64_t *)
-			luaL_checkudata(L, 1, fiberlib_name);
-		f = fiber_find(fid);
-	} else {
-		f = fiber();
-	}
+	struct fiber *f = lbox_get_fiber(L);
 	const char *status;
-	if (f == NULL || f->fid == 0) {
+	if (f == NULL) {
 		/* This fiber is dead. */
 		status = "dead";
 	} else if (f == fiber()) {
@@ -563,6 +582,42 @@ lbox_fiber_status(struct lua_State *L)
 	return 1;
 }
 
+/**
+ * Get fiber info: number of context switches, backtrace, id,
+ * total memory, used memory.
+ */
+static int
+lbox_fiber_object_info(struct lua_State *L)
+{
+	struct fiber *f = lbox_get_fiber(L);
+	if (f == NULL)
+		luaL_error(L, "the fiber is dead");
+#ifdef ENABLE_BACKTRACE
+	bool do_backtrace = lbox_do_backtrace(L);
+	if (do_backtrace) {
+		lua_newtable(L);
+		lbox_fiber_statof_map(f, L, true);
+	} else
+#endif /* ENABLE_BACKTRACE */
+	{
+		lua_newtable(L);
+		lbox_fiber_statof_map(f, L, false);
+	}
+	return 1;
+}
+
+static int
+lbox_fiber_csw(struct lua_State *L)
+{
+	struct fiber *f = lbox_get_fiber(L);
+	if (f == NULL) {
+		luaL_error(L, "the fiber is dead");
+	} else {
+		lua_pushinteger(L, f->csw);
+	}
+	return 1;
+}
+
 /**
  * Get or set fiber name.
  * With no arguments, gets or sets the current fiber
@@ -857,6 +912,8 @@ static const struct luaL_Reg lbox_fiber_meta [] = {
 	{"name", lbox_fiber_name},
 	{"cancel", lbox_fiber_cancel},
 	{"status", lbox_fiber_status},
+	{"info", lbox_fiber_object_info},
+	{"csw", lbox_fiber_csw},
 	{"testcancel", lbox_fiber_testcancel},
 	{"__serialize", lbox_fiber_serialize},
 	{"__tostring", lbox_fiber_tostring},
diff --git a/test/app/gh-5799-fiber-info.result b/test/app/gh-5799-fiber-info.result
new file mode 100644
index 0000000000..716ac9a246
--- /dev/null
+++ b/test/app/gh-5799-fiber-info.result
@@ -0,0 +1,80 @@
+-- test-run result file version 2
+test_run = require('test_run').new()
+ | ---
+ | ...
+fiber = require('fiber')
+ | ---
+ | ...
+json = require('json')
+ | ---
+ | ...
+
+function yielder(n) for i=1, n do fiber.yield() end end
+ | ---
+ | ...
+
+csw_check_counter = 0
+ | ---
+ | ...
+fibers = {}
+ | ---
+ | ...
+
+test_run:cmd('setopt delimiter ";"')
+ | ---
+ | - true
+ | ...
+for i=1,100 do
+    fibers[i] = fiber.new(function()
+        local start_csw = fiber.info()[fiber.self():id()].csw
+        for j=1,10 do
+            fiber.yield()
+            if j + start_csw == fiber.self():csw() and
+               j + start_csw == fiber.self():info().csw then
+                csw_check_counter = csw_check_counter + 1
+            end
+        end
+    end)
+    fibers[i]:set_joinable(true)
+end;
+ | ---
+ | ...
+for i=1,100 do
+    fibers[i]:join()
+end;
+ | ---
+ | ...
+test_run:cmd('setopt delimiter ""');
+ | ---
+ | - true
+ | ...
+csw_check_counter
+ | ---
+ | - 1000
+ | ...
+
+cond = fiber.cond()
+ | ---
+ | ...
+running = fiber.create(function() cond:wait() end)
+ | ---
+ | ...
+json.encode(running:info()) == json.encode(fiber.info()[running:id()])
+ | ---
+ | - true
+ | ...
+cond:signal()
+ | ---
+ | ...
+dead = fiber.create(function() end)
+ | ---
+ | ...
+
+dead:csw()
+ | ---
+ | - error: the fiber is dead
+ | ...
+dead:info()
+ | ---
+ | - error: the fiber is dead
+ | ...
diff --git a/test/app/gh-5799-fiber-info.test.lua b/test/app/gh-5799-fiber-info.test.lua
new file mode 100644
index 0000000000..588f760c40
--- /dev/null
+++ b/test/app/gh-5799-fiber-info.test.lua
@@ -0,0 +1,37 @@
+test_run = require('test_run').new()
+fiber = require('fiber')
+json = require('json')
+
+function yielder(n) for i=1, n do fiber.yield() end end
+
+csw_check_counter = 0
+fibers = {}
+
+test_run:cmd('setopt delimiter ";"')
+for i=1,100 do
+    fibers[i] = fiber.new(function()
+        local start_csw = fiber.info()[fiber.self():id()].csw
+        for j=1,10 do
+            fiber.yield()
+            if j + start_csw == fiber.self():csw() and
+               j + start_csw == fiber.self():info().csw then
+                csw_check_counter = csw_check_counter + 1
+            end
+        end
+    end)
+    fibers[i]:set_joinable(true)
+end;
+for i=1,100 do
+    fibers[i]:join()
+end;
+test_run:cmd('setopt delimiter ""');
+csw_check_counter
+
+cond = fiber.cond()
+running = fiber.create(function() cond:wait() end)
+json.encode(running:info()) == json.encode(fiber.info()[running:id()])
+cond:signal()
+dead = fiber.create(function() end)
+
+dead:csw()
+dead:info()
-- 
GitLab