[Tarantool-patches] [PATCH luajit 6/7] sysprof: introduce Lua API
Maxim Kokryashkin
max.kokryashkin at gmail.com
Wed Sep 8 20:50:49 MSK 2021
This commit introduces Lua API for sysprof, so it is now possible to
call it from anywhere in a Lua script. All of the parameters are passed
inside a single Lua table.
The following options are supported:
- mode: "D" (Default mode, only virtual machine state counters)
"L" (Leaf mode, shows the last frame on the stack)
"C" (Callchain mode, full stack dump)
- interval: time between samples
- path: Path to file in which profiling data should be stored
Part of tarantool/tarantool#781
---
src/lib_misc.c | 224 ++++++++++++++++++
.../misclib-sysprof-lapi.test.lua | 105 ++++++++
2 files changed, 329 insertions(+)
create mode 100644 test/tarantool-tests/misclib-sysprof-lapi.test.lua
diff --git a/src/lib_misc.c b/src/lib_misc.c
index a44476e0..a3423cdd 100644
--- a/src/lib_misc.c
+++ b/src/lib_misc.c
@@ -145,6 +145,218 @@ static int on_stop_cb_default(void *opt, uint8_t *buf)
return fclose(stream);
}
+/* ----- misc.sysprof module ---------------------------------------------- */
+
+/* Not loaded by default, use: local profile = require("misc.sysprof") */
+#define LJLIB_MODULE_misc_sysprof
+
+/* The default profiling interval equals to 11 ms. */
+#define SYSPROF_DEFAULT_INTERVAL (11)
+#define SYSPROF_DEFAULT_OUTPUT "sysprof.bin"
+
+int parse_sysprof_opts(lua_State *L, struct luam_sysprof_options *opt, int idx) {
+ GCtab *options = lj_lib_checktab(L, idx);
+
+ /* get profiling mode */
+ {
+ cTValue *mode_opt = lj_tab_getstr(options, lj_str_newlit(L, "mode"));
+ char mode = 0;
+ if (mode_opt) {
+ mode = *strVdata(mode_opt);
+ }
+
+ switch (mode) {
+ case 'D':
+ opt->mode = LUAM_SYSPROF_DEFAULT;
+ break;
+ case 'L':
+ opt->mode = LUAM_SYSPROF_LEAF;
+ break;
+ case 'C':
+ opt->mode = LUAM_SYSPROF_CALLGRAPH;
+ break;
+ default:
+ return SYSPROF_ERRUSE;
+ }
+ }
+
+ /* Get profiling interval. */
+ {
+ cTValue *interval = lj_tab_getstr(options, lj_str_newlit(L, "interval"));
+ opt->interval = SYSPROF_DEFAULT_INTERVAL;
+ if (interval) {
+ int32_t signed_interval = numberVint(interval);
+ if (signed_interval < 1) {
+ return SYSPROF_ERRUSE;
+ }
+ opt->interval = signed_interval;
+ }
+ }
+
+ return SYSPROF_SUCCESS;
+}
+
+int set_output_path(const char* path, struct luam_sysprof_options *opt) {
+ lua_assert(path != NULL);
+ struct profile_ctx *ctx = opt->ctx;
+ FILE *stream = fopen(path, "wb");
+ if(!stream) {
+ return SYSPROF_ERRIO;
+ }
+ ctx->stream = stream;
+ return SYSPROF_SUCCESS;
+}
+
+const char* parse_output_path(lua_State *L, struct luam_sysprof_options *opt, int idx) {
+ /* By default, sysprof writes events to a `sysprof.bin` file. */
+ const char *path = luaL_checkstring(L, idx);
+ return path ? path : SYSPROF_DEFAULT_OUTPUT;
+}
+
+int parse_options(lua_State *L, struct luam_sysprof_options *opt)
+{
+ int status = SYSPROF_SUCCESS;
+
+ switch(lua_gettop(L)) {
+ case 2:
+ if(!(lua_isstring(L, 1) && lua_istable(L, 2))) {
+ status = SYSPROF_ERRUSE;
+ break;
+ }
+ const char* path = parse_output_path(L, opt, 1);
+ status = set_output_path(path, opt);
+ if(status != SYSPROF_SUCCESS)
+ break;
+
+ status = parse_sysprof_opts(L, opt, 2);
+ break;
+ case 1:
+ if(!lua_istable(L, 1)) {
+ status = SYSPROF_ERRUSE;
+ break;
+ }
+ status = parse_sysprof_opts(L, opt, 1);
+ if(status != SYSPROF_SUCCESS)
+ break;
+ status = set_output_path(SYSPROF_DEFAULT_OUTPUT, opt);
+ break;
+ default:
+ status = SYSPROF_ERRUSE;
+ break;
+ }
+ return status;
+}
+
+int sysprof_error(lua_State *L, int status)
+{
+ switch (status) {
+ case SYSPROF_ERRUSE:
+ lua_pushnil(L);
+ lua_pushstring(L, err2msg(LJ_ERR_PROF_MISUSE));
+ lua_pushinteger(L, EINVAL);
+ return 3;
+ case SYSPROF_ERRRUN:
+ lua_pushnil(L);
+ lua_pushstring(L, err2msg(LJ_ERR_PROF_ISRUNNING));
+ lua_pushinteger(L, EINVAL);
+ return 3;
+ case SYSPROF_ERRSTOP:
+ lua_pushnil(L);
+ lua_pushstring(L, err2msg(LJ_ERR_PROF_NOTRUNNING));
+ lua_pushinteger(L, EINVAL);
+ return 3;
+ case SYSPROF_ERRIO:
+ return luaL_fileresult(L, 0, NULL);
+ default:
+ lua_assert(0);
+ return 0;
+ }
+}
+
+/* local res, err, errno = sysprof.start(options) */
+LJLIB_CF(misc_sysprof_start)
+{
+ int status = SYSPROF_SUCCESS;
+
+ struct luam_sysprof_options opt = {};
+ struct profile_ctx *ctx = NULL;
+
+ ctx = lj_mem_new(L, sizeof(*ctx));
+ ctx->g = G(L);
+ opt.ctx = ctx;
+ opt.buf = ctx->buf;
+ opt.len = STREAM_BUFFER_SIZE;
+
+ status = parse_options(L, &opt);
+ if (LJ_UNLIKELY(status != PROFILE_SUCCESS)) {
+ lj_mem_free(ctx->g, ctx, sizeof(*ctx));
+ return sysprof_error(L, status);
+ }
+
+ const struct luam_sysprof_config conf = {buffer_writer_default, on_stop_cb_default, NULL};
+ status = luaM_sysprof_configure(&conf);
+ if (LJ_UNLIKELY(status != PROFILE_SUCCESS)) {
+ return sysprof_error(L, status);
+ }
+
+ status = luaM_sysprof_start(L, &opt);
+ if (LJ_UNLIKELY(status != PROFILE_SUCCESS)) {
+ /* Allocated memory will be freed in on_stop callback. */
+ return sysprof_error(L, status);
+ }
+
+ lua_pushboolean(L, 1);
+ return 1;
+}
+
+/* local res, err, errno = profile.sysprof_stop() */
+LJLIB_CF(misc_sysprof_stop)
+{
+ int status = luaM_sysprof_stop(L);
+ if (LJ_UNLIKELY(status != PROFILE_SUCCESS)) {
+ return sysprof_error(L, status);
+ }
+ lua_pushboolean(L, 1);
+ return 1;
+}
+
+/* local counters, err, errno = sysprof.report() */
+LJLIB_CF(misc_sysprof_report)
+{
+ struct luam_sysprof_counters counters = {};
+ GCtab *data_tab = NULL;
+ GCtab *count_tab = NULL;
+
+ int status = luaM_sysprof_report(&counters);
+ if (status != SYSPROF_SUCCESS) {
+ return sysprof_error(L, status);
+ }
+
+ lua_createtable(L, 0, 3);
+ data_tab = tabV(L->top - 1);
+
+ setnumfield(L, data_tab, "samples", counters.samples);
+ setnumfield(L, data_tab, "overruns", counters.overruns);
+
+ lua_createtable(L, 0, LJ_VMST__MAX + 1);
+ count_tab = tabV(L->top - 1);
+
+ setnumfield(L, count_tab, "INTERP", counters.vmst_interp);
+ setnumfield(L, count_tab, "LFUNC", counters.vmst_lfunc);
+ setnumfield(L, count_tab, "FFUNC", counters.vmst_ffunc);
+ setnumfield(L, count_tab, "CFUNC", counters.vmst_cfunc);
+ setnumfield(L, count_tab, "GC", counters.vmst_gc);
+ setnumfield(L, count_tab, "EXIT", counters.vmst_exit);
+ setnumfield(L, count_tab, "RECORD", counters.vmst_record);
+ setnumfield(L, count_tab, "OPT", counters.vmst_opt);
+ setnumfield(L, count_tab, "ASM", counters.vmst_asm);
+ setnumfield(L, count_tab, "TRACE", counters.vmst_trace);
+
+ lua_setfield(L, -2, "vmstate");
+
+ return 1;
+}
+
/* ----- misc.memprof module ---------------------------------------------- */
#define LJLIB_MODULE_misc_memprof
@@ -237,7 +449,19 @@ LJLIB_CF(misc_memprof_stop)
LUALIB_API int luaopen_misc(struct lua_State *L)
{
+ int status = SYSPROF_SUCCESS;
+ struct luam_sysprof_config config = {};
+
+ config.writer = buffer_writer_default;
+ config.on_stop = on_stop_cb_default;
+ status = luaM_sysprof_configure(&config);
+ if (LJ_UNLIKELY(status != PROFILE_SUCCESS)) {
+ return sysprof_error(L, status);
+ }
+
LJ_LIB_REG(L, LUAM_MISCLIBNAME, misc);
LJ_LIB_REG(L, LUAM_MISCLIBNAME ".memprof", misc_memprof);
+ LJ_LIB_REG(L, LUAM_MISCLIBNAME ".sysprof", misc_sysprof);
+
return 1;
}
diff --git a/test/tarantool-tests/misclib-sysprof-lapi.test.lua b/test/tarantool-tests/misclib-sysprof-lapi.test.lua
new file mode 100644
index 00000000..17d83d1b
--- /dev/null
+++ b/test/tarantool-tests/misclib-sysprof-lapi.test.lua
@@ -0,0 +1,105 @@
+local tap = require("tap")
+
+local test = tap.test("misc-sysprof-lapi")
+test:plan(14)
+
+jit.off()
+jit.flush()
+
+local bufread = require "utils.bufread"
+local symtab = require "utils.symtab"
+
+local TMP_BINFILE = arg[0]:gsub(".+/([^/]+)%.test%.lua$", "%.%1.sysprofdata.tmp.bin")
+local BAD_PATH = arg[0]:gsub(".+/([^/]+)%.test%.lua$", "%1/sysprofdata.tmp.bin")
+
+local function payload()
+ local function fib(n)
+ if n <= 1 then
+ return n
+ end
+ return fib(n - 1) + fib(n - 2)
+ end
+ return fib(32)
+end
+
+local function generate_output(file, opts)
+ file = file or "sysprof.bin"
+ local res, err = misc.sysprof.start(file, opts)
+ assert(res, err)
+
+ payload()
+
+ res,err = misc.sysprof.stop()
+ assert(res, err)
+end
+--## GENERAL ###############################################################--
+
+--wrong profiling mode
+local res, err, errno = misc.sysprof.start{ mode = "A" }
+test:ok(res == nil and err:match("profiler misuse"))
+test:ok(type(errno) == "number")
+
+--already running
+res, err = misc.sysprof.start{ mode = "D" }
+assert(res, err)
+
+res, err, errno = misc.sysprof.start{ mode = "D" }
+print(err)
+test:ok(res == nil and err:match("profiler misuse"))
+test:ok(type(errno) == "number")
+
+res, err = misc.sysprof.stop()
+assert(res, err)
+
+--not running
+res, err, errno = misc.sysprof.stop()
+test:ok(res == nil and err:match("profiler is not running"))
+test:ok(type(errno) == "number")
+
+--bad path
+res, err, errno = misc.sysprof.start(BAD_PATH, { mode = "C" })
+test:ok(res == nil and err:match("No such file or directory"))
+test:ok(type(errno) == "number")
+
+--bad interval
+res, err, errno = misc.sysprof.start{ mode = "C", interval = -1 }
+test:ok(res == nil and err:match("profiler misuse"))
+test:ok(type(errno) == "number")
+--## DEFAULT MODE ##########################################################--
+
+res, err = pcall(generate_output, nil, { mode = "D", interval = 11 })
+if res == nil then
+ error(err)
+end
+
+local report = misc.sysprof.report()
+
+test:ok(report.samples > 0) -- TODO: more accurate test?
+test:ok(report.vmstate.LFUNC > 0)
+test:ok(report.vmstate.TRACE == 0)
+
+-- with very big interval
+res, err = pcall(generate_output, nil, { mode = "D", interval = 1000 })
+if res == nil then
+ error(err)
+end
+
+report = misc.sysprof.report()
+test:ok(report.samples == 0)
+
+--## CALL MODE #############################################################--
+
+res, err = pcall(
+ generate_output, TMP_BINFILE, { mode = "C", interval = 11 }
+)
+if res == nil then
+ error(err)
+end
+
+local reader = bufread.new(TMP_BINFILE)
+symtab.parse(reader)
+
+os.remove(TMP_BINFILE)
+
+jit.on()
+os.exit(test:check() and 0 or 1)
--
2.33.0
More information about the Tarantool-patches
mailing list