Tarantool development patches archive
 help / color / mirror / Atom feed
* [Tarantool-patches] [PATCH luajit v2 6/7] sysprof: introduce Lua API
@ 2022-03-16 18:57 Maxim Kokryashkin via Tarantool-patches
  2022-03-16 18:58 ` Maxim Kokryashkin via Tarantool-patches
  0 siblings, 1 reply; 2+ messages in thread
From: Maxim Kokryashkin via Tarantool-patches @ 2022-03-16 18:57 UTC (permalink / raw)
  To: tarantool-patches, imun, skaplun

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 in milliseconds
- path:     Path to file in which profiling data should be stored

Part of tarantool/tarantool#781
---
>> +#define SYSPROF_DEFAULT_INTERVAL (11)
>Why 11 and not 10, 9 or something else?
No reason for that. Changed it to 10, so it less questionable now.

>> +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:
>
>LJ_ERR_PROF_ISRUNNING and LJ_ERR_PROF_NOTRUNNING is defined only, when
>LJ_HASMEMPROF || LJ_HASSYSPROF, so it must be the corresponding ifdef
>here.
What value should be returned if we don't have neither sysprof
nor memprof?

 src/lib_misc.c                                | 238 +++++++++++++++++-
 .../misclib-sysprof-lapi.test.lua             | 118 +++++++++
 2 files changed, 348 insertions(+), 8 deletions(-)
 create mode 100644 test/tarantool-tests/misclib-sysprof-lapi.test.lua

diff --git a/src/lib_misc.c b/src/lib_misc.c
index 22d29d78..e93cf55b 100644
--- a/src/lib_misc.c
+++ b/src/lib_misc.c
@@ -109,14 +109,14 @@ static size_t buffer_writer_default(const void **buf_addr, size_t len,
   lua_assert(len <= STREAM_BUFFER_SIZE);
 
   for (;;) {
-    const size_t written = write(fd, data, len - write_total);
+    const ssize_t written = write(fd, data, len - write_total);
 
     if (LJ_UNLIKELY(written == -1)) {
       /* Re-tries write in case of EINTR. */
       if (errno != EINTR) {
-	/* Will be freed as whole chunk later. */
-	*buf_addr = NULL;
-	return write_total;
+  /* Will be freed as whole chunk later. */
+  *buf_addr = NULL;
+  return write_total;
       }
 
       errno = 0;
@@ -139,16 +139,236 @@ static size_t buffer_writer_default(const void **buf_addr, size_t len,
 /* Default on stop callback. Just close the corresponding descriptor. */
 static int on_stop_cb_default(void *opt, uint8_t *buf)
 {
-  struct profile_ctx *ctx = opt;
-  const int fd = ctx->fd;
+  struct profile_ctx *ctx = NULL;
+  int fd = 0;
+
+  if (opt == NULL) {
+    /* Nothing to do. */
+    return 0;
+  }
+
+  ctx = opt;
+  fd = ctx->fd;
   UNUSED(buf);
   lj_mem_free(ctx->g, ctx, sizeof(*ctx));
   return close(fd);
 }
 
+/* ----- misc.sysprof module ---------------------------------------------- */
+
+#define LJLIB_MODULE_misc_sysprof
+
+/* The default profiling interval equals to 11 ms. */
+#define SYSPROF_DEFAULT_INTERVAL 10
+#define SYSPROF_DEFAULT_OUTPUT "sysprof.bin"
+
+int set_output_path(const char *path, struct luam_sysprof_options *opt) {
+  struct profile_ctx *ctx = opt->ctx;
+  int fd = 0;
+  lua_assert(path != NULL);
+  fd = open(path, O_CREAT | O_WRONLY | O_TRUNC, 0644);
+  if(fd == -1) {
+    return SYSPROF_ERRIO;
+  }
+  ctx->fd = fd;
+  return SYSPROF_SUCCESS;
+}
+
+int parse_sysprof_opts(lua_State *L, struct luam_sysprof_options *opt, int idx) {
+  GCtab *options = lj_lib_checktab(L, idx);
+
+  /* Get profiling mode. */
+  {
+    const char *mode = NULL;
+
+    cTValue *mode_opt = lj_tab_getstr(options, lj_str_newlit(L, "mode"));
+    if (!mode_opt || !tvisstr(mode_opt)) {
+      return SYSPROF_ERRUSE;
+    }
+
+    mode = strVdata(mode_opt);
+    if (mode[1] != '\0')
+      return SYSPROF_ERRUSE;
+
+    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 && tvisnum(interval)) {
+      int32_t signed_interval = numberVint(interval);
+      if (signed_interval < 1)
+        return SYSPROF_ERRUSE;
+      opt->interval = signed_interval;
+    }
+  }
+
+  /* Get output path. */
+  if (opt->mode != LUAM_SYSPROF_DEFAULT)
+  {
+    const char *path = NULL;
+    struct profile_ctx *ctx = NULL;
+    int status = 0;
+
+    cTValue *pathtv = lj_tab_getstr(options, lj_str_newlit(L, "path"));
+    if (!pathtv)
+      path = SYSPROF_DEFAULT_OUTPUT;
+    else if (!tvisstr(pathtv))
+      return SYSPROF_ERRUSE;
+    else
+      path = strVdata(pathtv);
+
+    ctx = lj_mem_new(L, sizeof(*ctx));
+    ctx->g = G(L);
+    opt->ctx = ctx;
+    opt->buf = ctx->buf;
+    opt->len = STREAM_BUFFER_SIZE;
+
+    status = set_output_path(path, opt);
+    if (status != SYSPROF_SUCCESS) {
+      lj_mem_free(ctx->g, ctx, sizeof(*ctx));
+      return status;
+    }
+  }
+
+  return SYSPROF_SUCCESS;
+}
+
+int parse_options(lua_State *L, struct luam_sysprof_options *opt)
+{
+  if (lua_gettop(L) != 1)
+    return SYSPROF_ERRUSE;
+
+  if (!lua_istable(L, 1))
+    return SYSPROF_ERRUSE;
+
+  return parse_sysprof_opts(L, opt, 1);
+}
+
+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 luam_sysprof_config conf = {};
+
+
+  status = parse_options(L, &opt);
+  if (LJ_UNLIKELY(status != PROFILE_SUCCESS)) {
+    return sysprof_error(L, status);
+  }
+
+  conf.writer = buffer_writer_default;
+  conf.on_stop = on_stop_cb_default;
+  conf.backtracer = NULL;
+
+  status = luaM_sysprof_configure(&conf);
+  if (LJ_UNLIKELY(status != PROFILE_SUCCESS)) {
+    on_stop_cb_default(opt.ctx, opt.buf);
+    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
+
 /* local started, err, errno = misc.memprof.start(fname) */
 LJLIB_CF(misc_memprof_start)
 {
@@ -169,9 +389,9 @@ LJLIB_CF(misc_memprof_start)
   opt.len = STREAM_BUFFER_SIZE;
 
   ctx->g = G(L);
-  ctx->stream = fopen(fname, "wb");
+  ctx->fd = open(fname, O_CREAT | O_WRONLY | O_TRUNC);
 
-  if (ctx->stream == NULL) {
+  if (ctx->fd == -1) {
     lj_mem_free(ctx->g, ctx, sizeof(*ctx));
     return luaL_fileresult(L, 0, fname);
   }
@@ -240,5 +460,7 @@ LUALIB_API int luaopen_misc(struct lua_State *L)
 {
   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..b4c9cb7b
--- /dev/null
+++ b/test/tarantool-tests/misclib-sysprof-lapi.test.lua
@@ -0,0 +1,118 @@
+-- Sysprof is implemented for x86 and x64 architectures only.
+require("utils").skipcond(
+  jit.arch ~= "x86" and jit.arch ~= "x64",
+  jit.arch.." architecture is NIY for sysprof"
+)
+
+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(opts)
+  local res, err = misc.sysprof.start(opts)
+  assert(res, err)
+
+  payload()
+
+  res,err = misc.sysprof.stop()
+  assert(res, err)
+end
+
+local function check_mode(mode, interval)
+  local res = pcall(
+    generate_output,
+    { mode = mode, interval = interval, path = TMP_BINFILE }
+  )
+
+  if not res then
+    test:fail(mode .. ' mode with interval ' .. interval)
+    os.remove(TMP_BINFILE)
+  end
+
+  local reader = bufread.new(TMP_BINFILE)
+  symtab.parse(reader)
+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" }
+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({ mode = "C", path = BAD_PATH })
+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
+
+if not pcall(generate_output, { mode = "D", interval = 11 }) then
+  test:fail('`default` mode with interval 11')
+end
+
+local report = misc.sysprof.report()
+
+test:ok(report.samples > 0)
+test:ok(report.vmstate.LFUNC > 0)
+test:ok(report.vmstate.TRACE == 0)
+
+-- With very big interval.
+if not pcall(generate_output, { mode = "D", interval = 1000 }) then
+  test:fail('`default` mode with interval 1000')
+end
+
+report = misc.sysprof.report()
+test:ok(report.samples == 0)
+
+-- LEAF MODE
+check_mode("L", 11)
+
+-- CALL MODE
+check_mode("C", 11)
+
+os.remove(TMP_BINFILE)
+
+jit.on()
+os.exit(test:check() and 0 or 1)
-- 
2.35.1


^ permalink raw reply	[flat|nested] 2+ messages in thread

end of thread, other threads:[~2022-03-16 18:58 UTC | newest]

Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-03-16 18:57 [Tarantool-patches] [PATCH luajit v2 6/7] sysprof: introduce Lua API Maxim Kokryashkin via Tarantool-patches
2022-03-16 18:58 ` Maxim Kokryashkin via Tarantool-patches

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox