<HTML><BODY><div>New branch: <a href="https://github.com/tarantool/luajit/tree/fckxorg/gh-781-platform-and-lua-profiler-full-ci">https://github.com/tarantool/luajit/tree/fckxorg/gh-781-platform-and-lua-profiler-full-ci</a></div><div> </div><blockquote style="border-left:1px solid #0857A6; margin:10px; padding:0 0 0 10px;">Среда, 16 марта 2022, 21:57 +03:00 от Maxim Kokryashkin <max.kokryashkin@gmail.com>:<br> <div id=""><div class="js-helper js-readmsg-msg"><div><div id="style_16474570460315102492_BODY">This commit introduces Lua API for sysprof, so it is now possible to<br>call it from anywhere in a Lua script. All of the parameters are passed<br>inside a single Lua table.<br>The following options are supported:<br>- mode: "D" (Default mode, only virtual machine state counters)<br>            "L" (Leaf mode, shows the last frame on the stack)<br>            "C" (Callchain mode, full stack dump)<br>- interval: time between samples in milliseconds<br>- path: Path to file in which profiling data should be stored<br><br>Part of tarantool/tarantool#781<br>---<br>>> +#define SYSPROF_DEFAULT_INTERVAL (11)<br>>Why 11 and not 10, 9 or something else?<br>No reason for that. Changed it to 10, so it less questionable now.<br><br>>> +int sysprof_error(lua_State *L, int status)<br>>> +{<br>>> + switch (status) {<br>>> + case SYSPROF_ERRUSE:<br>>> + lua_pushnil(L);<br>>> + lua_pushstring(L, err2msg(LJ_ERR_PROF_MISUSE));<br>>> + lua_pushinteger(L, EINVAL);<br>>> + return 3;<br>>> + case SYSPROF_ERRRUN:<br>><br>>LJ_ERR_PROF_ISRUNNING and LJ_ERR_PROF_NOTRUNNING is defined only, when<br>>LJ_HASMEMPROF || LJ_HASSYSPROF, so it must be the corresponding ifdef<br>>here.<br>What value should be returned if we don't have neither sysprof<br>nor memprof?<br><br> src/lib_misc.c | 238 +++++++++++++++++-<br> .../misclib-sysprof-lapi.test.lua | 118 +++++++++<br> 2 files changed, 348 insertions(+), 8 deletions(-)<br> create mode 100644 test/tarantool-tests/misclib-sysprof-lapi.test.lua<br><br>diff --git a/src/lib_misc.c b/src/lib_misc.c<br>index 22d29d78..e93cf55b 100644<br>--- a/src/lib_misc.c<br>+++ b/src/lib_misc.c<br>@@ -109,14 +109,14 @@ static size_t buffer_writer_default(const void **buf_addr, size_t len,<br>   lua_assert(len <= STREAM_BUFFER_SIZE);<br> <br>   for (;;) {<br>- const size_t written = write(fd, data, len - write_total);<br>+ const ssize_t written = write(fd, data, len - write_total);<br> <br>     if (LJ_UNLIKELY(written == -1)) {<br>       /* Re-tries write in case of EINTR. */<br>       if (errno != EINTR) {<br>- /* Will be freed as whole chunk later. */<br>- *buf_addr = NULL;<br>- return write_total;<br>+ /* Will be freed as whole chunk later. */<br>+ *buf_addr = NULL;<br>+ return write_total;<br>       }<br> <br>       errno = 0;<br>@@ -139,16 +139,236 @@ static size_t buffer_writer_default(const void **buf_addr, size_t len,<br> /* Default on stop callback. Just close the corresponding descriptor. */<br> static int on_stop_cb_default(void *opt, uint8_t *buf)<br> {<br>- struct profile_ctx *ctx = opt;<br>- const int fd = ctx->fd;<br>+ struct profile_ctx *ctx = NULL;<br>+ int fd = 0;<br>+<br>+ if (opt == NULL) {<br>+ /* Nothing to do. */<br>+ return 0;<br>+ }<br>+<br>+ ctx = opt;<br>+ fd = ctx->fd;<br>   UNUSED(buf);<br>   lj_mem_free(ctx->g, ctx, sizeof(*ctx));<br>   return close(fd);<br> }<br> <br>+/* ----- misc.sysprof module ---------------------------------------------- */<br>+<br>+#define LJLIB_MODULE_misc_sysprof<br>+<br>+/* The default profiling interval equals to 11 ms. */<br>+#define SYSPROF_DEFAULT_INTERVAL 10<br>+#define SYSPROF_DEFAULT_OUTPUT "sysprof.bin"<br>+<br>+int set_output_path(const char *path, struct luam_sysprof_options *opt) {<br>+ struct profile_ctx *ctx = opt->ctx;<br>+ int fd = 0;<br>+ lua_assert(path != NULL);<br>+ fd = open(path, O_CREAT | O_WRONLY | O_TRUNC, 0644);<br>+ if(fd == -1) {<br>+ return SYSPROF_ERRIO;<br>+ }<br>+ ctx->fd = fd;<br>+ return SYSPROF_SUCCESS;<br>+}<br>+<br>+int parse_sysprof_opts(lua_State *L, struct luam_sysprof_options *opt, int idx) {<br>+ GCtab *options = lj_lib_checktab(L, idx);<br>+<br>+ /* Get profiling mode. */<br>+ {<br>+ const char *mode = NULL;<br>+<br>+ cTValue *mode_opt = lj_tab_getstr(options, lj_str_newlit(L, "mode"));<br>+ if (!mode_opt || !tvisstr(mode_opt)) {<br>+ return SYSPROF_ERRUSE;<br>+ }<br>+<br>+ mode = strVdata(mode_opt);<br>+ if (mode[1] != '\0')<br>+ return SYSPROF_ERRUSE;<br>+<br>+ switch (*mode) {<br>+ case 'D':<br>+ opt->mode = LUAM_SYSPROF_DEFAULT;<br>+ break;<br>+ case 'L':<br>+ opt->mode = LUAM_SYSPROF_LEAF;<br>+ break;<br>+ case 'C':<br>+ opt->mode = LUAM_SYSPROF_CALLGRAPH;<br>+ break;<br>+ default:<br>+ return SYSPROF_ERRUSE;<br>+ }<br>+ }<br>+<br>+ /* Get profiling interval. */<br>+ {<br>+ cTValue *interval = lj_tab_getstr(options, lj_str_newlit(L, "interval"));<br>+ opt->interval = SYSPROF_DEFAULT_INTERVAL;<br>+ if (interval && tvisnum(interval)) {<br>+ int32_t signed_interval = numberVint(interval);<br>+ if (signed_interval < 1)<br>+ return SYSPROF_ERRUSE;<br>+ opt->interval = signed_interval;<br>+ }<br>+ }<br>+<br>+ /* Get output path. */<br>+ if (opt->mode != LUAM_SYSPROF_DEFAULT)<br>+ {<br>+ const char *path = NULL;<br>+ struct profile_ctx *ctx = NULL;<br>+ int status = 0;<br>+<br>+ cTValue *pathtv = lj_tab_getstr(options, lj_str_newlit(L, "path"));<br>+ if (!pathtv)<br>+ path = SYSPROF_DEFAULT_OUTPUT;<br>+ else if (!tvisstr(pathtv))<br>+ return SYSPROF_ERRUSE;<br>+ else<br>+ path = strVdata(pathtv);<br>+<br>+ ctx = lj_mem_new(L, sizeof(*ctx));<br>+ ctx->g = G(L);<br>+ opt->ctx = ctx;<br>+ opt->buf = ctx->buf;<br>+ opt->len = STREAM_BUFFER_SIZE;<br>+<br>+ status = set_output_path(path, opt);<br>+ if (status != SYSPROF_SUCCESS) {<br>+ lj_mem_free(ctx->g, ctx, sizeof(*ctx));<br>+ return status;<br>+ }<br>+ }<br>+<br>+ return SYSPROF_SUCCESS;<br>+}<br>+<br>+int parse_options(lua_State *L, struct luam_sysprof_options *opt)<br>+{<br>+ if (lua_gettop(L) != 1)<br>+ return SYSPROF_ERRUSE;<br>+<br>+ if (!lua_istable(L, 1))<br>+ return SYSPROF_ERRUSE;<br>+<br>+ return parse_sysprof_opts(L, opt, 1);<br>+}<br>+<br>+int sysprof_error(lua_State *L, int status)<br>+{<br>+ switch (status) {<br>+ case SYSPROF_ERRUSE:<br>+ lua_pushnil(L);<br>+ lua_pushstring(L, err2msg(LJ_ERR_PROF_MISUSE));<br>+ lua_pushinteger(L, EINVAL);<br>+ return 3;<br>+ case SYSPROF_ERRRUN:<br>+ lua_pushnil(L);<br>+ lua_pushstring(L, err2msg(LJ_ERR_PROF_ISRUNNING));<br>+ lua_pushinteger(L, EINVAL);<br>+ return 3;<br>+ case SYSPROF_ERRSTOP:<br>+ lua_pushnil(L);<br>+ lua_pushstring(L, err2msg(LJ_ERR_PROF_NOTRUNNING));<br>+ lua_pushinteger(L, EINVAL);<br>+ return 3;<br>+ case SYSPROF_ERRIO:<br>+ return luaL_fileresult(L, 0, NULL);<br>+ default:<br>+ lua_assert(0);<br>+ return 0;<br>+ }<br>+}<br>+<br>+/* local res, err, errno = sysprof.start(options) */<br>+LJLIB_CF(misc_sysprof_start)<br>+{<br>+ int status = SYSPROF_SUCCESS;<br>+<br>+ struct luam_sysprof_options opt = {};<br>+ struct luam_sysprof_config conf = {};<br>+<br>+<br>+ status = parse_options(L, &opt);<br>+ if (LJ_UNLIKELY(status != PROFILE_SUCCESS)) {<br>+ return sysprof_error(L, status);<br>+ }<br>+<br>+ conf.writer = buffer_writer_default;<br>+ conf.on_stop = on_stop_cb_default;<br>+ conf.backtracer = NULL;<br>+<br>+ status = luaM_sysprof_configure(&conf);<br>+ if (LJ_UNLIKELY(status != PROFILE_SUCCESS)) {<br>+ on_stop_cb_default(opt.ctx, opt.buf);<br>+ return sysprof_error(L, status);<br>+ }<br>+<br>+ status = luaM_sysprof_start(L, &opt);<br>+ if (LJ_UNLIKELY(status != PROFILE_SUCCESS))<br>+ /* Allocated memory will be freed in on_stop callback. */<br>+ return sysprof_error(L, status);<br>+<br>+ lua_pushboolean(L, 1);<br>+ return 1;<br>+}<br>+<br>+/* local res, err, errno = profile.sysprof_stop() */<br>+LJLIB_CF(misc_sysprof_stop)<br>+{<br>+ int status = luaM_sysprof_stop(L);<br>+ if (LJ_UNLIKELY(status != PROFILE_SUCCESS))<br>+ return sysprof_error(L, status);<br>+<br>+ lua_pushboolean(L, 1);<br>+ return 1;<br>+}<br>+<br>+/* local counters, err, errno = sysprof.report() */<br>+LJLIB_CF(misc_sysprof_report)<br>+{<br>+ struct luam_sysprof_counters counters = {};<br>+ GCtab *data_tab = NULL;<br>+ GCtab *count_tab = NULL;<br>+<br>+ int status = luaM_sysprof_report(&counters);<br>+ if (status != SYSPROF_SUCCESS)<br>+ return sysprof_error(L, status);<br>+<br>+ lua_createtable(L, 0, 3);<br>+ data_tab = tabV(L->top - 1);<br>+<br>+ setnumfield(L, data_tab, "samples", counters.samples);<br>+ setnumfield(L, data_tab, "overruns", counters.overruns);<br>+<br>+ lua_createtable(L, 0, LJ_VMST__MAX + 1);<br>+ count_tab = tabV(L->top - 1);<br>+<br>+ setnumfield(L, count_tab, "INTERP", counters.vmst_interp);<br>+ setnumfield(L, count_tab, "LFUNC", counters.vmst_lfunc);<br>+ setnumfield(L, count_tab, "FFUNC", counters.vmst_ffunc);<br>+ setnumfield(L, count_tab, "CFUNC", counters.vmst_cfunc);<br>+ setnumfield(L, count_tab, "GC", counters.vmst_gc);<br>+ setnumfield(L, count_tab, "EXIT", counters.vmst_exit);<br>+ setnumfield(L, count_tab, "RECORD", counters.vmst_record);<br>+ setnumfield(L, count_tab, "OPT", counters.vmst_opt);<br>+ setnumfield(L, count_tab, "ASM", counters.vmst_asm);<br>+ setnumfield(L, count_tab, "TRACE", counters.vmst_trace);<br>+<br>+ lua_setfield(L, -2, "vmstate");<br>+<br>+ return 1;<br>+}<br>+<br> /* ----- misc.memprof module ---------------------------------------------- */<br> <br> #define LJLIB_MODULE_misc_memprof<br>+<br> /* local started, err, errno = misc.memprof.start(fname) */<br> LJLIB_CF(misc_memprof_start)<br> {<br>@@ -169,9 +389,9 @@ LJLIB_CF(misc_memprof_start)<br>   opt.len = STREAM_BUFFER_SIZE;<br> <br>   ctx->g = G(L);<br>- ctx->stream = fopen(fname, "wb");<br>+ ctx->fd = open(fname, O_CREAT | O_WRONLY | O_TRUNC);<br> <br>- if (ctx->stream == NULL) {<br>+ if (ctx->fd == -1) {<br>     lj_mem_free(ctx->g, ctx, sizeof(*ctx));<br>     return luaL_fileresult(L, 0, fname);<br>   }<br>@@ -240,5 +460,7 @@ LUALIB_API int luaopen_misc(struct lua_State *L)<br> {<br>   LJ_LIB_REG(L, LUAM_MISCLIBNAME, misc);<br>   LJ_LIB_REG(L, LUAM_MISCLIBNAME ".memprof", misc_memprof);<br>+ LJ_LIB_REG(L, LUAM_MISCLIBNAME ".sysprof", misc_sysprof);<br>+<br>   return 1;<br> }<br>diff --git a/test/tarantool-tests/misclib-sysprof-lapi.test.lua b/test/tarantool-tests/misclib-sysprof-lapi.test.lua<br>new file mode 100644<br>index 00000000..b4c9cb7b<br>--- /dev/null<br>+++ b/test/tarantool-tests/misclib-sysprof-lapi.test.lua<br>@@ -0,0 +1,118 @@<br>+-- Sysprof is implemented for x86 and x64 architectures only.<br>+require("utils").skipcond(<br>+ jit.arch ~= "x86" and jit.arch ~= "x64",<br>+ jit.arch.." architecture is NIY for sysprof"<br>+)<br>+<br>+local tap = require("tap")<br>+<br>+local test = tap.test("misc-sysprof-lapi")<br>+test:plan(14)<br>+<br>+jit.off()<br>+jit.flush()<br>+<br>+local bufread = require("utils.bufread")<br>+local symtab = require("utils.symtab")<br>+<br>+local TMP_BINFILE = arg[0]:gsub(".+/([^/]+)%.test%.lua$", "%.%1.sysprofdata.tmp.bin")<br>+local BAD_PATH = arg[0]:gsub(".+/([^/]+)%.test%.lua$", "%1/sysprofdata.tmp.bin")<br>+<br>+local function payload()<br>+ local function fib(n)<br>+ if n <= 1 then<br>+ return n<br>+ end<br>+ return fib(n - 1) + fib(n - 2)<br>+ end<br>+ return fib(32)<br>+end<br>+<br>+local function generate_output(opts)<br>+ local res, err = misc.sysprof.start(opts)<br>+ assert(res, err)<br>+<br>+ payload()<br>+<br>+ res,err = misc.sysprof.stop()<br>+ assert(res, err)<br>+end<br>+<br>+local function check_mode(mode, interval)<br>+ local res = pcall(<br>+ generate_output,<br>+ { mode = mode, interval = interval, path = TMP_BINFILE }<br>+ )<br>+<br>+ if not res then<br>+ test:fail(mode .. ' mode with interval ' .. interval)<br>+ os.remove(TMP_BINFILE)<br>+ end<br>+<br>+ local reader = bufread.new(TMP_BINFILE)<br>+ symtab.parse(reader)<br>+end<br>+<br>+-- GENERAL<br>+<br>+-- Wrong profiling mode.<br>+local res, err, errno = misc.sysprof.start{ mode = "A" }<br>+test:ok(res == nil and err:match("profiler misuse"))<br>+test:ok(type(errno) == "number")<br>+<br>+-- Already running.<br>+res, err = misc.sysprof.start{ mode = "D" }<br>+assert(res, err)<br>+<br>+res, err, errno = misc.sysprof.start{ mode = "D" }<br>+test:ok(res == nil and err:match("profiler misuse"))<br>+test:ok(type(errno) == "number")<br>+<br>+res, err = misc.sysprof.stop()<br>+assert(res, err)<br>+<br>+-- Not running.<br>+res, err, errno = misc.sysprof.stop()<br>+test:ok(res == nil and err:match("profiler is not running"))<br>+test:ok(type(errno) == "number")<br>+<br>+-- Bad path.<br>+res, err, errno = misc.sysprof.start({ mode = "C", path = BAD_PATH })<br>+test:ok(res == nil and err:match("No such file or directory"))<br>+test:ok(type(errno) == "number")<br>+<br>+-- Bad interval.<br>+res, err, errno = misc.sysprof.start{ mode = "C", interval = -1 }<br>+test:ok(res == nil and err:match("profiler misuse"))<br>+test:ok(type(errno) == "number")<br>+<br>+-- DEFAULT MODE<br>+<br>+if not pcall(generate_output, { mode = "D", interval = 11 }) then<br>+ test:fail('`default` mode with interval 11')<br>+end<br>+<br>+local report = misc.sysprof.report()<br>+<br>+test:ok(report.samples > 0)<br>+test:ok(report.vmstate.LFUNC > 0)<br>+test:ok(report.vmstate.TRACE == 0)<br>+<br>+-- With very big interval.<br>+if not pcall(generate_output, { mode = "D", interval = 1000 }) then<br>+ test:fail('`default` mode with interval 1000')<br>+end<br>+<br>+report = misc.sysprof.report()<br>+test:ok(report.samples == 0)<br>+<br>+-- LEAF MODE<br>+check_mode("L", 11)<br>+<br>+-- CALL MODE<br>+check_mode("C", 11)<br>+<br>+os.remove(TMP_BINFILE)<br>+<br>+jit.on()<br>+os.exit(test:check() and 0 or 1)<br>--<br>2.35.1</div></div></div></div></blockquote><div> </div></BODY></HTML>