From: Maxim Kokryashkin via Tarantool-patches <tarantool-patches@dev.tarantool.org> To: tarantool-patches@dev.tarantool.org, imun@tarantool.org, skaplun@tarantool.org Subject: [Tarantool-patches] [PATCH luajit 6/7] sysprof: introduce Lua API Date: Wed, 8 Sep 2021 20:50:49 +0300 [thread overview] Message-ID: <7fac25ca504796c478cf5ba51f65631378bc8c4f.1631122521.git.m.kokryashkin@tarantool.org> (raw) In-Reply-To: <cover.1631122521.git.m.kokryashkin@tarantool.org> 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
next prev parent reply other threads:[~2021-09-08 17:54 UTC|newest] Thread overview: 20+ messages / expand[flat|nested] mbox.gz Atom feed top 2021-09-08 17:50 [Tarantool-patches] [PATCH luajit 0/7] misc: introduce sysprof Maxim Kokryashkin via Tarantool-patches 2021-09-08 17:50 ` [Tarantool-patches] [PATCH luajit 1/7] core: save current frame top to lj_obj Maxim Kokryashkin via Tarantool-patches 2021-09-20 17:21 ` Sergey Kaplun via Tarantool-patches 2021-09-08 17:50 ` [Tarantool-patches] [PATCH luajit 2/7] core: separate the profiling timer from lj_profile Maxim Kokryashkin via Tarantool-patches 2021-09-21 11:13 ` Sergey Kaplun via Tarantool-patches 2021-09-23 11:37 ` Mikhail Shishatskiy via Tarantool-patches 2021-09-08 17:50 ` [Tarantool-patches] [PATCH luajit 3/7] memprof: move symtab to a separate module Maxim Kokryashkin via Tarantool-patches 2021-09-22 7:51 ` Sergey Kaplun via Tarantool-patches 2021-09-22 8:14 ` Sergey Kaplun via Tarantool-patches 2021-09-23 14:51 ` [Tarantool-patches] [PATCH luajit v2] " Maxim Kokryashkin via Tarantool-patches 2021-09-08 17:50 ` [Tarantool-patches] [PATCH luajit 4/7] core: introduce lua and platform profiler Maxim Kokryashkin via Tarantool-patches 2021-09-29 6:53 ` Sergey Kaplun via Tarantool-patches 2021-09-08 17:50 ` [Tarantool-patches] [PATCH luajit 5/7] memprof: add profile common section Maxim Kokryashkin via Tarantool-patches 2021-10-05 10:48 ` Sergey Kaplun via Tarantool-patches 2021-10-06 19:15 ` [Tarantool-patches] [PATCH luajit v2] " Maxim Kokryashkin via Tarantool-patches 2021-09-08 17:50 ` Maxim Kokryashkin via Tarantool-patches [this message] 2021-10-05 15:36 ` [Tarantool-patches] [PATCH luajit 6/7] sysprof: introduce Lua API Sergey Kaplun via Tarantool-patches 2021-09-08 17:50 ` [Tarantool-patches] [PATCH luajit 7/7] tools: introduce parsers for sysprof Maxim Kokryashkin via Tarantool-patches 2021-10-07 11:28 ` Sergey Kaplun via Tarantool-patches 2022-04-07 12:15 ` [Tarantool-patches] [PATCH luajit 0/7] misc: introduce sysprof Sergey Kaplun via Tarantool-patches
Reply instructions: You may reply publicly to this message via plain-text email using any one of the following methods: * Save the following mbox file, import it into your mail client, and reply-to-all from there: mbox Avoid top-posting and favor interleaved quoting: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style * Reply using the --to, --cc, and --in-reply-to switches of git-send-email(1): git send-email \ --in-reply-to=7fac25ca504796c478cf5ba51f65631378bc8c4f.1631122521.git.m.kokryashkin@tarantool.org \ --to=tarantool-patches@dev.tarantool.org \ --cc=imun@tarantool.org \ --cc=max.kokryashkin@gmail.com \ --cc=skaplun@tarantool.org \ --subject='Re: [Tarantool-patches] [PATCH luajit 6/7] sysprof: introduce Lua API' \ /path/to/YOUR_REPLY https://kernel.org/pub/software/scm/git/docs/git-send-email.html * If your mail client supports setting the In-Reply-To header via mailto: links, try the mailto: link
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox