[Tarantool-patches] [PATCH v3 2/2] misc: add C and Lua API for platform metrics
Sergey Kaplun
skaplun at tarantool.org
Sun Sep 20 20:12:49 MSK 2020
This patch introduces both C and Lua API for LuaJIT platform
metrics implemented in scope of the previous patch. New <lmisclib.h>
header provides <luaM_metrics> C interface that fills the given
<luam_metrics> structure with the platform metrics. Additionally
<misc> module is loaded to Lua space and provides <getmetrics>
method that yields the corresponding metrics via table.
Part of tarantool/tarantool#5187
---
Makefile | 2 +-
src/Makefile | 5 +-
src/Makefile.dep | 14 +-
src/lib_init.c | 2 +
src/lib_misc.c | 76 +++
src/lj_mapi.c | 65 +++
src/ljamalg.c | 2 +
src/lmisclib.h | 71 +++
src/luaconf.h | 1 +
test/clib-misclib-getmetrics.test.lua | 188 +++++++
test/clib-misclib-getmetrics/CMakeLists.txt | 1 +
test/clib-misclib-getmetrics/testgetmetrics.c | 287 +++++++++++
test/lib-misc-getmetrics.test.lua | 459 ++++++++++++++++++
13 files changed, 1165 insertions(+), 8 deletions(-)
create mode 100644 src/lib_misc.c
create mode 100644 src/lj_mapi.c
create mode 100644 src/lmisclib.h
create mode 100755 test/clib-misclib-getmetrics.test.lua
create mode 100644 test/clib-misclib-getmetrics/CMakeLists.txt
create mode 100644 test/clib-misclib-getmetrics/testgetmetrics.c
create mode 100755 test/lib-misc-getmetrics.test.lua
diff --git a/Makefile b/Makefile
index 0f93308..4a56917 100644
--- a/Makefile
+++ b/Makefile
@@ -84,7 +84,7 @@ FILE_A= libluajit.a
FILE_SO= libluajit.so
FILE_MAN= luajit.1
FILE_PC= luajit.pc
-FILES_INC= lua.h lualib.h lauxlib.h luaconf.h lua.hpp luajit.h
+FILES_INC= lua.h lualib.h lauxlib.h luaconf.h lua.hpp luajit.h lmisclib.h
FILES_JITLIB= bc.lua bcsave.lua dump.lua p.lua v.lua zone.lua \
dis_x86.lua dis_x64.lua dis_arm.lua dis_arm64.lua \
dis_arm64be.lua dis_ppc.lua dis_mips.lua dis_mipsel.lua \
diff --git a/src/Makefile b/src/Makefile
index 827d4a4..2786348 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -480,13 +480,14 @@ LJVM_BOUT= $(LJVM_S)
LJVM_MODE= elfasm
LJLIB_O= lib_base.o lib_math.o lib_bit.o lib_string.o lib_table.o \
- lib_io.o lib_os.o lib_package.o lib_debug.o lib_jit.o lib_ffi.o
+ lib_io.o lib_os.o lib_package.o lib_debug.o lib_jit.o lib_ffi.o \
+ lib_misc.o
LJLIB_C= $(LJLIB_O:.o=.c)
LJCORE_O= lj_gc.o lj_err.o lj_char.o lj_bc.o lj_obj.o lj_buf.o \
lj_str.o lj_tab.o lj_func.o lj_udata.o lj_meta.o lj_debug.o \
lj_state.o lj_dispatch.o lj_vmevent.o lj_vmmath.o lj_strscan.o \
- lj_strfmt.o lj_strfmt_num.o lj_api.o lj_profile.o \
+ lj_strfmt.o lj_strfmt_num.o lj_api.o lj_mapi.o lj_profile.o \
lj_lex.o lj_parse.o lj_bcread.o lj_bcwrite.o lj_load.o \
lj_ir.o lj_opt_mem.o lj_opt_fold.o lj_opt_narrow.o \
lj_opt_dce.o lj_opt_loop.o lj_opt_split.o lj_opt_sink.o \
diff --git a/src/Makefile.dep b/src/Makefile.dep
index 2b1cb5e..556314e 100644
--- a/src/Makefile.dep
+++ b/src/Makefile.dep
@@ -18,7 +18,7 @@ lib_ffi.o: lib_ffi.c lua.h luaconf.h lauxlib.h lualib.h lj_obj.h lj_def.h \
lj_ctype.h lj_cparse.h lj_cdata.h lj_cconv.h lj_carith.h lj_ccall.h \
lj_ccallback.h lj_clib.h lj_strfmt.h lj_ff.h lj_ffdef.h lj_lib.h \
lj_libdef.h
-lib_init.o: lib_init.c lua.h luaconf.h lauxlib.h lualib.h lj_arch.h
+lib_init.o: lib_init.c lua.h luaconf.h lauxlib.h lualib.h lmisclib.h lj_arch.h
lib_io.o: lib_io.c lua.h luaconf.h lauxlib.h lualib.h lj_obj.h lj_def.h \
lj_arch.h lj_gc.h lj_err.h lj_errmsg.h lj_buf.h lj_str.h lj_state.h \
lj_strfmt.h lj_ff.h lj_ffdef.h lj_lib.h lj_libdef.h
@@ -29,6 +29,8 @@ lib_jit.o: lib_jit.c lua.h luaconf.h lauxlib.h lualib.h lj_obj.h lj_def.h \
lj_vm.h lj_vmevent.h lj_lib.h luajit.h lj_libdef.h
lib_math.o: lib_math.c lua.h luaconf.h lauxlib.h lualib.h lj_obj.h \
lj_def.h lj_arch.h lj_lib.h lj_vm.h lj_libdef.h
+lib_misc.o: lib_misc.c lua.h luaconf.h lmisclib.h lj_obj.h lj_def.h lj_arch.h \
+ lj_str.h lj_tab.h lj_lib.h lj_libdef.h
lib_os.o: lib_os.c lua.h luaconf.h lauxlib.h lualib.h lj_obj.h lj_def.h \
lj_arch.h lj_gc.h lj_err.h lj_errmsg.h lj_buf.h lj_str.h lj_lib.h \
lj_libdef.h
@@ -137,6 +139,8 @@ lj_lib.o: lj_lib.c lauxlib.h lua.h luaconf.h lj_obj.h lj_def.h lj_arch.h \
lj_load.o: lj_load.c lua.h luaconf.h lauxlib.h lj_obj.h lj_def.h \
lj_arch.h lj_gc.h lj_err.h lj_errmsg.h lj_buf.h lj_str.h lj_func.h \
lj_frame.h lj_bc.h lj_vm.h lj_lex.h lj_bcdump.h lj_parse.h
+lj_mapi.o: lj_mapi.c lua.h luaconf.h lmisclib.h lj_obj.h lj_def.h lj_arch.h \
+ lj_dispatch.h lj_bc.h lj_jit.h lj_ir.h
lj_mcode.o: lj_mcode.c lj_obj.h lua.h luaconf.h lj_def.h lj_arch.h \
lj_gc.h lj_err.h lj_errmsg.h lj_jit.h lj_ir.h lj_mcode.h lj_trace.h \
lj_dispatch.h lj_bc.h lj_traceerr.h lj_vm.h
@@ -215,9 +219,9 @@ ljamalg.o: ljamalg.c lua.h luaconf.h lauxlib.h lj_gc.c lj_obj.h lj_def.h \
lj_func.c lj_udata.c lj_meta.c lj_strscan.h lj_lib.h lj_debug.c \
lj_state.c lj_lex.h lj_alloc.h luajit.h lj_dispatch.c lj_ccallback.h \
lj_profile.h lj_vmevent.c lj_vmevent.h lj_vmmath.c lj_strscan.c \
- lj_strfmt.c lj_strfmt_num.c lj_api.c lj_profile.c lj_lex.c lualib.h \
- lj_parse.h lj_parse.c lj_bcread.c lj_bcdump.h lj_bcwrite.c lj_load.c \
- lj_ctype.c lj_cdata.c lj_cconv.h lj_cconv.c lj_ccall.c lj_ccall.h \
+ lj_strfmt.c lj_strfmt_num.c lj_api.c lj_mapi.c lmisclib.h lj_profile.c \
+ lj_lex.c lualib.h lj_parse.h lj_parse.c lj_bcread.c lj_bcdump.h lj_bcwrite.c \
+ lj_load.c lj_ctype.c lj_cdata.c lj_cconv.h lj_cconv.c lj_ccall.c lj_ccall.h \
lj_ccallback.c lj_target.h lj_target_*.h lj_mcode.h lj_carith.c \
lj_carith.h lj_clib.c lj_clib.h lj_cparse.c lj_cparse.h lj_lib.c lj_ir.c \
lj_ircall.h lj_iropt.h lj_opt_mem.c lj_opt_fold.c lj_folddef.h \
@@ -227,7 +231,7 @@ ljamalg.o: ljamalg.c lua.h luaconf.h lauxlib.h lj_gc.c lj_obj.h lj_def.h \
lj_emit_*.h lj_asm_*.h lj_trace.c lj_gdbjit.h lj_gdbjit.c lj_alloc.c \
lib_aux.c lib_base.c lj_libdef.h lib_math.c lib_string.c lib_table.c \
lib_io.c lib_os.c lib_package.c lib_debug.c lib_bit.c lib_jit.c \
- lib_ffi.c lib_init.c
+ lib_ffi.c lib_misc.c lib_init.c
luajit.o: luajit.c lua.h luaconf.h lauxlib.h lualib.h luajit.h lj_arch.h
host/buildvm.o: host/buildvm.c host/buildvm.h lj_def.h lua.h luaconf.h \
lj_arch.h lj_obj.h lj_def.h lj_arch.h lj_gc.h lj_obj.h lj_bc.h lj_ir.h \
diff --git a/src/lib_init.c b/src/lib_init.c
index 2ed370e..664aa7d 100644
--- a/src/lib_init.c
+++ b/src/lib_init.c
@@ -12,6 +12,7 @@
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
+#include "lmisclib.h"
#include "lj_arch.h"
@@ -26,6 +27,7 @@ static const luaL_Reg lj_lib_load[] = {
{ LUA_DBLIBNAME, luaopen_debug },
{ LUA_BITLIBNAME, luaopen_bit },
{ LUA_JITLIBNAME, luaopen_jit },
+ { LUAM_MISCLIBNAME, luaopen_misc },
{ NULL, NULL }
};
diff --git a/src/lib_misc.c b/src/lib_misc.c
new file mode 100644
index 0000000..287b059
--- /dev/null
+++ b/src/lib_misc.c
@@ -0,0 +1,76 @@
+/*
+** Miscellaneous Lua extensions library.
+**
+** Major portions taken verbatim or adapted from the LuaVela interpreter.
+** Copyright (C) 2015-2019 IPONWEB Ltd.
+*/
+
+#define lib_misc_c
+#define LUA_LIB
+
+#include "lua.h"
+#include "lmisclib.h"
+
+#include "lj_obj.h"
+#include "lj_str.h"
+#include "lj_tab.h"
+#include "lj_lib.h"
+
+/* ------------------------------------------------------------------------ */
+
+static LJ_AINLINE void setnumfield(struct lua_State *L, GCtab *t,
+ const char *name, int64_t val)
+{
+ setnumV(lj_tab_setstr(L, t, lj_str_newz(L, name)), (double)val);
+}
+
+#define LJLIB_MODULE_misc
+
+LJLIB_CF(misc_getmetrics)
+{
+ struct luam_Metrics metrics;
+ lua_createtable(L, 0, 22);
+ GCtab *m = tabV(L->top - 1);
+
+ luaM_metrics(L, &metrics);
+
+ setnumfield(L, m, "strhash_hit", metrics.strhash_hit);
+ setnumfield(L, m, "strhash_miss", metrics.strhash_miss);
+
+ setnumfield(L, m, "gc_strnum", metrics.gc_strnum);
+ setnumfield(L, m, "gc_tabnum", metrics.gc_tabnum);
+ setnumfield(L, m, "gc_udatanum", metrics.gc_udatanum);
+ setnumfield(L, m, "gc_cdatanum", metrics.gc_cdatanum);
+
+ setnumfield(L, m, "gc_whitenum", metrics.gc_whitenum);
+ setnumfield(L, m, "gc_graynum", metrics.gc_graynum);
+ setnumfield(L, m, "gc_blacknum", metrics.gc_blacknum);
+
+ setnumfield(L, m, "gc_total", metrics.gc_total);
+ setnumfield(L, m, "gc_freed", metrics.gc_freed);
+ setnumfield(L, m, "gc_allocated", metrics.gc_allocated);
+
+ setnumfield(L, m, "gc_steps_pause", metrics.gc_steps_pause);
+ setnumfield(L, m, "gc_steps_propagate", metrics.gc_steps_propagate);
+ setnumfield(L, m, "gc_steps_atomic", metrics.gc_steps_atomic);
+ setnumfield(L, m, "gc_steps_sweepstring", metrics.gc_steps_sweepstring);
+ setnumfield(L, m, "gc_steps_sweep", metrics.gc_steps_sweep);
+ setnumfield(L, m, "gc_steps_finalize", metrics.gc_steps_finalize);
+
+ setnumfield(L, m, "jit_snap_restore", metrics.jit_snap_restore);
+ setnumfield(L, m, "jit_trace_abort", metrics.jit_trace_abort);
+ setnumfield(L, m, "jit_mcode_size", metrics.jit_mcode_size);
+ setnumfield(L, m, "jit_trace_num", metrics.jit_trace_num);
+
+ return 1;
+}
+
+/* ------------------------------------------------------------------------ */
+
+#include "lj_libdef.h"
+
+LUALIB_API int luaopen_misc(struct lua_State *L)
+{
+ LJ_LIB_REG(L, LUAM_MISCLIBNAME, misc);
+ return 1;
+}
diff --git a/src/lj_mapi.c b/src/lj_mapi.c
new file mode 100644
index 0000000..a3fb645
--- /dev/null
+++ b/src/lj_mapi.c
@@ -0,0 +1,65 @@
+/*
+** Miscellaneous public C API extensions.
+**
+** Major portions taken verbatim or adapted from the LuaVela.
+** Copyright (C) 2015-2019 IPONWEB Ltd.
+*/
+
+#include "lua.h"
+#include "lmisclib.h"
+
+#include "lj_obj.h"
+#include "lj_dispatch.h"
+
+#if LJ_HASJIT
+#include "lj_jit.h"
+#endif
+
+LUAMISC_API void luaM_metrics(lua_State *L, struct luam_Metrics *metrics)
+{
+ lua_assert(metrics != NULL);
+ global_State *g = G(L);
+ GCState *gc = &g->gc;
+#if LJ_HASJIT
+ jit_State *J = G2J(g);
+#endif
+
+ metrics->strhash_hit = g->strhash_hit;
+ metrics->strhash_miss = g->strhash_miss;
+
+ metrics->gc_strnum = g->strnum;
+ metrics->gc_tabnum = gc->tabnum;
+ metrics->gc_udatanum = gc->udatanum;
+#if LJ_HASFFI
+ metrics->gc_cdatanum = gc->cdatanum;
+#else
+ metrics->gc_cdatanum = 0;
+#endif
+
+ metrics->gc_whitenum = gc->whitenum;
+ metrics->gc_graynum = gc->graynum;
+ metrics->gc_blacknum = gc->blacknum;
+
+ metrics->gc_total = gc->total;
+ metrics->gc_freed = gc->freed;
+ metrics->gc_allocated = gc->allocated;
+
+ metrics->gc_steps_pause = gc->state_count[GCSpause];
+ metrics->gc_steps_propagate = gc->state_count[GCSpropagate];
+ metrics->gc_steps_atomic = gc->state_count[GCSatomic];
+ metrics->gc_steps_sweepstring = gc->state_count[GCSsweepstring];
+ metrics->gc_steps_sweep = gc->state_count[GCSsweep];
+ metrics->gc_steps_finalize = gc->state_count[GCSfinalize];
+
+#if LJ_HASJIT
+ metrics->jit_snap_restore = J->nsnaprestore;
+ metrics->jit_trace_abort = J->ntraceabort;
+ metrics->jit_mcode_size = J->szallmcarea;
+ metrics->jit_trace_num = J->tracenum;
+#else
+ metrics->jit_snap_restore = 0;
+ metrics->jit_trace_abort = 0;
+ metrics->jit_mcode_size = 0;
+ metrics->jit_trace_num = 0;
+#endif
+}
diff --git a/src/ljamalg.c b/src/ljamalg.c
index f1f2862..371bbb6 100644
--- a/src/ljamalg.c
+++ b/src/ljamalg.c
@@ -48,6 +48,7 @@
#include "lj_strfmt.c"
#include "lj_strfmt_num.c"
#include "lj_api.c"
+#include "lj_mapi.c"
#include "lj_profile.c"
#include "lj_lex.c"
#include "lj_parse.c"
@@ -93,5 +94,6 @@
#include "lib_bit.c"
#include "lib_jit.c"
#include "lib_ffi.c"
+#include "lib_misc.c"
#include "lib_init.c"
diff --git a/src/lmisclib.h b/src/lmisclib.h
new file mode 100644
index 0000000..e4970b5
--- /dev/null
+++ b/src/lmisclib.h
@@ -0,0 +1,71 @@
+/*
+** Miscellaneous public C API extensions.
+**
+** Major portions taken verbatim or adapted from the LuaVela.
+** Copyright (C) 2015-2019 IPONWEB Ltd.
+*/
+
+#ifndef _LMISCLIB_H
+#define _LMISCLIB_H
+
+#include "lua.h"
+
+/* API for obtaining various platform metrics. */
+
+struct luam_Metrics {
+ /* Strings amount found in string hash instead of allocation of new one. */
+ size_t strhash_hit;
+ /* Strings amount allocated and put into string hash. */
+ size_t strhash_miss;
+
+ /* Amount of allocated string objects. */
+ size_t gc_strnum;
+ /* Amount of allocated table objects. */
+ size_t gc_tabnum;
+ /* Amount of allocated udata objects. */
+ size_t gc_udatanum;
+ /* Amount of allocated cdata objects. */
+ size_t gc_cdatanum;
+
+ /* Amount of white objects. */
+ size_t gc_whitenum;
+ /* Amount of gray objects. */
+ size_t gc_graynum;
+ /* Amount of black objects. */
+ size_t gc_blacknum;
+
+ /* Memory currently allocated. */
+ size_t gc_total;
+ /* Total amount of freed memory. */
+ size_t gc_freed;
+ /* Total amount of allocated memory. */
+ size_t gc_allocated;
+
+ /* Count of incremental GC steps per state. */
+ size_t gc_steps_pause;
+ size_t gc_steps_propagate;
+ size_t gc_steps_atomic;
+ size_t gc_steps_sweepstring;
+ size_t gc_steps_sweep;
+ size_t gc_steps_finalize;
+
+ /*
+ ** Overall number of snap restores (amount of guard assertions
+ ** leading to stopping trace executions and trace exits,
+ ** that are not stitching with other traces).
+ */
+ size_t jit_snap_restore;
+ /* Overall number of abort traces. */
+ size_t jit_trace_abort;
+ /* Total size of all allocated machine code areas. */
+ size_t jit_mcode_size;
+ /* Amount of JIT traces. */
+ unsigned int jit_trace_num;
+};
+
+LUAMISC_API void luaM_metrics(lua_State *L, struct luam_Metrics *metrics);
+
+#define LUAM_MISCLIBNAME "misc"
+LUALIB_API int luaopen_misc(lua_State *L);
+
+#endif /* _LMISCLIB_H */
diff --git a/src/luaconf.h b/src/luaconf.h
index 60cb928..8029040 100644
--- a/src/luaconf.h
+++ b/src/luaconf.h
@@ -144,6 +144,7 @@
#endif
#define LUALIB_API LUA_API
+#define LUAMISC_API LUA_API
/* Support for internal assertions. */
#if defined(LUA_USE_ASSERT) || defined(LUA_USE_APICHECK)
diff --git a/test/clib-misclib-getmetrics.test.lua b/test/clib-misclib-getmetrics.test.lua
new file mode 100755
index 0000000..840840c
--- /dev/null
+++ b/test/clib-misclib-getmetrics.test.lua
@@ -0,0 +1,188 @@
+#!/usr/bin/env tarantool
+
+local file = debug.getinfo(1, "S").source:sub(2)
+local filepath = file:match("(.*/)")
+local soext = jit.os == "OSX" and "dylib" or "so"
+package.cpath = filepath..'clib-misclib-getmetrics/?.'..soext..";"
+ ..package.cpath
+
+local jit_opt_default_hotloop = 56
+local jit_opt_default_hotexit = 10
+local jit_opt_default_level = 3
+local jit_opt_default_minstitch = 0
+
+local tap = require('tap')
+
+local test = tap.test("clib-misc-getmetrics")
+test:plan(13)
+
+local testgetmetrics = require("testgetmetrics")
+
+test:ok(testgetmetrics.base())
+test:ok(testgetmetrics.gc_allocated_freed())
+test:ok(testgetmetrics.gc_colors_base())
+test:ok(testgetmetrics.gc_colors_strempty())
+
+-- Upvalues are never gray.
+tbar_t = {}
+test:ok(testgetmetrics.gc_colors_tbar(function()
+ local tabnew = require"table.new"
+ for i =1, 100 do
+ tbar_t.x = tabnew(i,0)
+ end
+ collectgarbage("collect")
+ tbar_t = nil
+end))
+
+test:ok(testgetmetrics.gc_steps())
+
+test:ok(testgetmetrics.objcount(function()
+ local table_new = require("table.new")
+ local ffi = require("ffi")
+
+ jit.opt.start(0)
+
+ local placeholder = {
+ str = {},
+ tab = {},
+ udata = {},
+ cdata = {},
+ }
+
+ -- Separate objects creations to separate jit traces.
+ for i = 1, 1000 do
+ table.insert(placeholder.str, tostring(i))
+ end
+
+ for i = 1, 1000 do
+ table.insert(placeholder.tab, table_new(i, 0))
+ end
+
+ for i = 1, 1000 do
+ table.insert(placeholder.udata, newproxy())
+ end
+
+ for i = 1, 1000 do
+ -- Check counting of VLA/VLS/aligned cdata.
+ table.insert(placeholder.cdata, ffi.new("char[?]", 4))
+ end
+
+ for i = 1, 1000 do
+ -- Check counting of non-VLA/VLS/aligned cdata.
+ table.insert(placeholder.cdata, ffi.new("uint64_t", i))
+ end
+
+ placeholder = nil
+ -- Restore default jit settings.
+ jit.opt.start(jit_opt_default_level)
+end))
+
+-- Compiled loop with a direct exit to the interpreter.
+test:ok(testgetmetrics.snap_restores(function()
+ jit.opt.start(0, "hotloop=2")
+
+ local old_metrics = misc.getmetrics()
+
+ local sum = 0
+ for i = 1, 20 do
+ sum = sum + i
+ end
+
+ local new_metrics = misc.getmetrics()
+
+ -- Restore default jit settings.
+ jit.opt.start(jit_opt_default_level, "hotloop="..jit_opt_default_hotloop)
+
+ -- A single snapshot restoration happened on loop finish.
+ return 1
+end))
+
+-- Compiled loop with a side exit which does not get compiled.
+test:ok(testgetmetrics.snap_restores(function()
+ jit.opt.start(0, "hotloop=2", "hotexit=2", "minstitch=15")
+
+ local function foo(i)
+ -- math.fmod is not yet compiled!
+ return i <= 5 and i or math.fmod(i, 11)
+ end
+
+ local sum = 0
+ for i = 1, 10 do
+ sum = sum + foo(i)
+ end
+
+ -- Restore default jit settings.
+ jit.opt.start(jit_opt_default_level, "hotloop="..jit_opt_default_hotloop,
+ "hotexit="..jit_opt_default_hotexit,
+ "minstitch="..jit_opt_default_minstitch)
+
+ -- Side exits from the root trace could not get compiled.
+ return 5
+end))
+
+-- Compiled loop with a side exit which gets compiled.
+test:ok(testgetmetrics.snap_restores(function()
+ -- Optimization level is important here as `loop` optimization
+ -- may unroll the loop body and insert +1 side exit.
+ jit.opt.start(0, "hotloop=5", "hotexit=5")
+
+ local function foo(i)
+ return i <= 10 and i or tostring(i)
+ end
+
+ local sum = 0
+ for i = 1, 20 do
+ sum = sum + foo(i)
+ end
+
+ -- Restore default jit settings.
+ jit.opt.start(jit_opt_default_level, "hotloop="..jit_opt_default_hotloop,
+ "hotexit="..jit_opt_default_hotexit)
+
+ -- 5 side exits to the interpreter before trace gets hot
+ -- and compiled
+ -- 1 side exit on loop end
+ return 6
+end))
+
+-- Compiled scalar trace with a direct exit to the interpreter.
+test:ok(testgetmetrics.snap_restores(function()
+ -- For calls it will be 2 * hotloop (see lj_dispatch.{c,h}
+ -- and hotcall at vm_*.dasc).
+ jit.opt.start(3, "hotloop=2", "hotexit=3")
+
+ local function foo(i)
+ return i <= 15 and i or tostring(i)
+ end
+
+ foo(1) -- interp only
+ foo(2) -- interp only
+ foo(3) -- interp only
+ foo(4) -- compile trace during this call
+ foo(5) -- follow the trace
+ foo(6) -- follow the trace
+ foo(7) -- follow the trace
+ foo(8) -- follow the trace
+ foo(9) -- follow the trace
+ foo(10) -- follow the trace
+
+ -- Simply 2 side exits from the trace:
+ foo(20)
+ foo(21)
+
+ -- Restore default jit settings.
+ jit.opt.start(jit_opt_default_level, "hotloop="..jit_opt_default_hotloop,
+ "hotexit="..jit_opt_default_hotexit)
+ return 2
+end))
+
+test:ok(testgetmetrics.strhash())
+
+test:ok(testgetmetrics.tracenum_base(function()
+ local sum = 0
+ for i = 1, 200 do
+ sum = sum + i
+ end
+ -- Compiled only 1 loop as new trace.
+ return 1
+end))
diff --git a/test/clib-misclib-getmetrics/CMakeLists.txt b/test/clib-misclib-getmetrics/CMakeLists.txt
new file mode 100644
index 0000000..e7cc8f8
--- /dev/null
+++ b/test/clib-misclib-getmetrics/CMakeLists.txt
@@ -0,0 +1 @@
+build_lualib(testgetmetrics testgetmetrics.c)
diff --git a/test/clib-misclib-getmetrics/testgetmetrics.c b/test/clib-misclib-getmetrics/testgetmetrics.c
new file mode 100644
index 0000000..85f0505
--- /dev/null
+++ b/test/clib-misclib-getmetrics/testgetmetrics.c
@@ -0,0 +1,287 @@
+#include <lua.h>
+#include <luajit.h>
+#include <lauxlib.h>
+
+#include <lmisclib.h>
+
+#include <assert.h>
+
+static int base(lua_State *L)
+{
+ struct luam_Metrics metrics;
+ luaM_metrics(L, &metrics);
+
+ /* Just check API. */
+ assert((ssize_t)metrics.strhash_hit >= 0);
+ assert((ssize_t)metrics.strhash_miss >= 0);
+
+ assert((ssize_t)metrics.gc_strnum >= 0);
+ assert((ssize_t)metrics.gc_tabnum >= 0);
+ assert((ssize_t)metrics.gc_udatanum >= 0);
+ assert((ssize_t)metrics.gc_cdatanum >= 0);
+
+ assert((ssize_t)metrics.gc_whitenum >= 0);
+ assert((ssize_t)metrics.gc_graynum >= 0);
+ assert((ssize_t)metrics.gc_blacknum >= 0);
+
+ assert((ssize_t)metrics.gc_total >= 0);
+ assert((ssize_t)metrics.gc_freed >= 0);
+ assert((ssize_t)metrics.gc_allocated >= 0);
+
+ assert((ssize_t)metrics.gc_steps_pause >= 0);
+ assert((ssize_t)metrics.gc_steps_propagate >= 0);
+ assert((ssize_t)metrics.gc_steps_atomic >= 0);
+ assert((ssize_t)metrics.gc_steps_sweepstring >= 0);
+ assert((ssize_t)metrics.gc_steps_sweep >= 0);
+ assert((ssize_t)metrics.gc_steps_finalize >= 0);
+
+ assert((ssize_t)metrics.jit_snap_restore >= 0);
+ assert((ssize_t)metrics.jit_trace_abort >= 0);
+ assert((ssize_t)metrics.jit_mcode_size >= 0);
+ assert((ssize_t)metrics.jit_trace_num >= 0);
+
+ lua_pushboolean(L, 1);
+ return 1;
+}
+
+static int gc_allocated_freed(lua_State *L)
+{
+ struct luam_Metrics oldm, newm;
+ /* Force up garbage collect all dead objects. */
+ lua_gc(L, LUA_GCCOLLECT, 0);
+
+ luaM_metrics(L, &oldm);
+ /* Simple garbage generation. */
+ if (luaL_dostring(L, "local i = 0 for j = 1, 10 do i = i + j end"))
+ luaL_error(L, "failed to translate Lua code snippet");
+ lua_gc(L, LUA_GCCOLLECT, 0);
+ luaM_metrics(L, &newm);
+ assert(newm.gc_allocated - oldm.gc_allocated > 0);
+ assert(newm.gc_freed - oldm.gc_freed > 0);
+
+ lua_pushboolean(L, 1);
+ return 1;
+}
+
+static int gc_colors_base(lua_State *L)
+{
+ struct luam_Metrics metrics;
+ lua_gc(L, LUA_GCCOLLECT, 0);
+ luaM_metrics(L, &metrics);
+ /* There are no non-white objects after full gc cycle. */
+ assert(metrics.gc_blacknum == 0);
+ assert(metrics.gc_graynum == 0);
+ lua_pushboolean(L, 1);
+ return 1;
+}
+
+static int gc_colors_strempty(lua_State *L)
+{
+ /*
+ * Empty string is specially recolored at the end
+ * of atomic phase.
+ */
+ lua_pushstring(L, "");
+ /* Check that none of the internal asserts will fail. */
+ for (int i = 0; i < 100; i++)
+ lua_gc(L, LUA_GCCOLLECT, 0);
+ lua_pop(L, 1);
+ lua_pushboolean(L, 1);
+ return 1;
+}
+
+static int gc_colors_tbar(lua_State *L)
+{
+ int n = lua_gettop(L);
+ if (n != 1 || !lua_isfunction(L, 1))
+ luaL_error(L, "incorrect arguments: 1 function is required");
+ /* Check that none of the internal asserts will fail. */
+ lua_call(L, 0, 0);
+ lua_pushboolean(L, 1);
+ return 1;
+}
+
+static int gc_steps(lua_State *L)
+{
+ struct luam_Metrics oldm, newm;
+ /*
+ * Some garbage has already happened before the next line,
+ * i.e. during fronted processing lua test chunk.
+ * Let's put a full garbage collection cycle on top
+ * of that, and confirm that non-null values are reported
+ * (we are not yet interested in actual numbers):
+ */
+ luaM_metrics(L, &oldm);
+ lua_gc(L, LUA_GCCOLLECT, 0);
+ assert(oldm.gc_steps_pause > 0);
+ assert(oldm.gc_steps_propagate > 0);
+ assert(oldm.gc_steps_atomic > 0);
+ assert(oldm.gc_steps_sweepstring > 0);
+ assert(oldm.gc_steps_sweep > 0);
+ /* Nothing to finalize, skipped. */
+ assert(oldm.gc_steps_finalize == 0);
+
+ /*
+ * As long as we don't create new Lua objects
+ * consequent call should return the same values:
+ */
+ luaM_metrics(L, &newm);
+ assert(newm.gc_steps_pause - oldm.gc_steps_pause > 0);
+ assert(newm.gc_steps_propagate - oldm.gc_steps_propagate > 0);
+ assert(newm.gc_steps_atomic - oldm.gc_steps_atomic > 0);
+ assert(newm.gc_steps_sweepstring - oldm.gc_steps_sweepstring > 0);
+ assert(newm.gc_steps_sweep - oldm.gc_steps_sweep > 0);
+ /* Nothing to finalize, skipped. */
+ assert(newm.gc_steps_finalize == 0);
+ oldm = newm;
+
+ /*
+ * Now the last phase: run full GC once and make sure that
+ * everything is being reported as expected:
+ */
+ lua_gc(L, LUA_GCCOLLECT, 0);
+ luaM_metrics(L, &newm);
+ assert(newm.gc_steps_pause - oldm.gc_steps_pause == 1);
+ assert(newm.gc_steps_propagate - oldm.gc_steps_propagate >= 1);
+ assert(newm.gc_steps_atomic - oldm.gc_steps_atomic == 1);
+ assert(newm.gc_steps_sweepstring - oldm.gc_steps_sweepstring >= 1);
+ assert(newm.gc_steps_sweep - oldm.gc_steps_sweep >= 1);
+ /* Nothing to finalize, skipped. */
+ assert(newm.gc_steps_finalize == 0);
+ oldm = newm;
+
+ /*
+ * Now let's run three GC cycles to ensure that
+ * zero-to-one transition was not a lucky coincidence.
+ */
+ lua_gc(L, LUA_GCCOLLECT, 0);
+ lua_gc(L, LUA_GCCOLLECT, 0);
+ lua_gc(L, LUA_GCCOLLECT, 0);
+ luaM_metrics(L, &newm);
+ assert(newm.gc_steps_pause - oldm.gc_steps_pause == 3);
+ assert(newm.gc_steps_propagate - oldm.gc_steps_propagate >= 3);
+ assert(newm.gc_steps_atomic - oldm.gc_steps_atomic == 3);
+ assert(newm.gc_steps_sweepstring - oldm.gc_steps_sweepstring >= 3);
+ assert(newm.gc_steps_sweep - oldm.gc_steps_sweep >= 3);
+ /* Nothing to finalize, skipped. */
+ assert(newm.gc_steps_finalize == 0);
+
+ lua_pushboolean(L, 1);
+ return 1;
+}
+
+static int objcount(lua_State *L)
+{
+ struct luam_Metrics oldm, newm;
+ int n = lua_gettop(L);
+ if (n != 1 || !lua_isfunction(L, 1))
+ luaL_error(L, "incorrect argument: 1 function is required");
+
+ /* Force up garbage collect all dead objects. */
+ lua_gc(L, LUA_GCCOLLECT, 0);
+
+ luaM_metrics(L, &oldm);
+ /* Generate garbage. */
+ lua_call(L, 0, 0);
+ lua_gc(L, LUA_GCCOLLECT, 0);
+ luaM_metrics(L, &newm);
+ assert(newm.gc_strnum - oldm.gc_strnum == 0);
+ assert(newm.gc_tabnum - oldm.gc_tabnum == 0);
+ assert(newm.gc_udatanum - oldm.gc_udatanum == 0);
+ assert(newm.gc_cdatanum - oldm.gc_cdatanum == 0);
+
+ lua_pushboolean(L, 1);
+ return 1;
+}
+
+static int snap_restores(lua_State *L)
+{
+ struct luam_Metrics oldm, newm;
+ int n = lua_gettop(L);
+ if (n != 1 || !lua_isfunction(L, 1))
+ luaL_error(L, "incorrect arguments: 1 function is required");
+
+ luaM_metrics(L, &oldm);
+ /* Generate snapshots. */
+ lua_call(L, 0, 1);
+ n = lua_gettop(L);
+ if (n != 1 || !lua_isnumber(L, 1))
+ luaL_error(L, "incorrect return value: 1 number is required");
+ size_t snap_restores = lua_tonumber(L, 1);
+ luaM_metrics(L, &newm);
+ assert(newm.jit_snap_restore - oldm.jit_snap_restore == snap_restores);
+
+ lua_pushboolean(L, 1);
+ return 1;
+}
+
+static int strhash(lua_State *L)
+{
+ struct luam_Metrics oldm, newm;
+ lua_pushstring(L, "strhash_hit");
+ luaM_metrics(L, &oldm);
+ lua_pushstring(L, "strhash_hit");
+ lua_pushstring(L, "new_str");
+ luaM_metrics(L, &newm);
+ assert(newm.strhash_hit - oldm.strhash_hit == 1);
+ assert(newm.strhash_miss - oldm.strhash_miss == 1);
+ lua_pop(L, 3);
+ lua_pushboolean(L, 1);
+ return 1;
+}
+
+static int tracenum_base(lua_State *L)
+{
+ struct luam_Metrics metrics;
+ int n = lua_gettop(L);
+ if (n != 1 || !lua_isfunction(L, 1))
+ luaL_error(L, "incorrect arguments: 1 function is required");
+
+ luaJIT_setmode(L, 0, LUAJIT_MODE_OFF);
+ luaJIT_setmode(L, 0, LUAJIT_MODE_FLUSH);
+ /* Force up garbage collect all dead objects. */
+ lua_gc(L, LUA_GCCOLLECT, 0);
+
+ luaM_metrics(L, &metrics);
+ assert(metrics.jit_trace_num == 0);
+
+ luaJIT_setmode(L, 0, LUAJIT_MODE_ON);
+ /* Generate traces. */
+ lua_call(L, 0, 1);
+ n = lua_gettop(L);
+ if (n != 1 || !lua_isnumber(L, 1))
+ luaL_error(L, "incorrect return value: 1 number is required");
+ size_t jit_trace_num = lua_tonumber(L, 1);
+ luaM_metrics(L, &metrics);
+ assert(metrics.jit_trace_num == jit_trace_num);
+
+ luaJIT_setmode(L, 0, LUAJIT_MODE_FLUSH);
+ /* Force up garbage collect all dead objects. */
+ lua_gc(L, LUA_GCCOLLECT, 0);
+ luaM_metrics(L, &metrics);
+ assert(metrics.jit_trace_num == 0);
+
+ luaJIT_setmode(L, 0, LUAJIT_MODE_ON);
+ lua_pushboolean(L, 1);
+ return 1;
+}
+
+static const struct luaL_Reg testgetmetrics[] = {
+ {"base", base},
+ {"gc_allocated_freed", gc_allocated_freed},
+ {"gc_colors_base", gc_colors_base},
+ {"gc_colors_strempty", gc_colors_strempty},
+ {"gc_colors_tbar", gc_colors_tbar},
+ {"gc_steps", gc_steps},
+ {"objcount", objcount},
+ {"snap_restores", snap_restores},
+ {"strhash", strhash},
+ {"tracenum_base", tracenum_base},
+ {NULL, NULL}
+};
+
+LUA_API int luaopen_testgetmetrics(lua_State *L)
+{
+ luaL_register(L, "testgetmetrics", testgetmetrics);
+ return 1;
+}
diff --git a/test/lib-misc-getmetrics.test.lua b/test/lib-misc-getmetrics.test.lua
new file mode 100755
index 0000000..66423ab
--- /dev/null
+++ b/test/lib-misc-getmetrics.test.lua
@@ -0,0 +1,459 @@
+#!/usr/bin/env tarantool
+
+-- This is a part of tarantool/luajit testing suite.
+-- Major portions taken verbatim or adapted from the LuaVela testing suit.
+-- Copyright (C) 2015-2019 IPONWEB Ltd.
+
+local tap = require('tap')
+
+local test = tap.test("lib-misc-getmetrics")
+test:plan(13)
+
+local jit_opt_default_hotloop = 56
+local jit_opt_default_hotexit = 10
+local jit_opt_default_level = 3
+local jit_opt_default_minstitch = 0
+
+-- Test Lua API.
+test:test("base", function(subtest)
+ subtest:plan(22)
+ local metrics = misc.getmetrics()
+ subtest:ok(metrics.strhash_hit >= 0)
+ subtest:ok(metrics.strhash_miss >= 0)
+
+ subtest:ok(metrics.gc_strnum >= 0)
+ subtest:ok(metrics.gc_tabnum >= 0)
+ subtest:ok(metrics.gc_udatanum >= 0)
+ subtest:ok(metrics.gc_cdatanum >= 0)
+
+ subtest:ok(metrics.gc_whitenum >= 2)
+ subtest:ok(metrics.gc_graynum >= 0)
+ subtest:ok(metrics.gc_blacknum >= 0)
+
+ subtest:ok(metrics.gc_total >= 0)
+ subtest:ok(metrics.gc_freed >= 0)
+ subtest:ok(metrics.gc_allocated >= 0)
+
+ subtest:ok(metrics.gc_steps_pause >= 0)
+ subtest:ok(metrics.gc_steps_propagate >= 0)
+ subtest:ok(metrics.gc_steps_atomic >= 0)
+ subtest:ok(metrics.gc_steps_sweepstring >= 0)
+ subtest:ok(metrics.gc_steps_sweep >= 0)
+ subtest:ok(metrics.gc_steps_finalize >= 0)
+
+ subtest:ok(metrics.jit_snap_restore >= 0)
+ subtest:ok(metrics.jit_trace_abort >= 0)
+ subtest:ok(metrics.jit_mcode_size >= 0)
+ subtest:ok(metrics.jit_trace_num >= 0)
+end)
+
+test:test("gc-colors-base", function(subtest)
+ subtest:plan(2)
+
+ -- Force up garbage collect all dead objects.
+ collectgarbage("collect")
+
+ -- There are no non-white objects after full gc cycle.
+ local metrics = misc.getmetrics()
+ subtest:is(metrics.gc_graynum, 0)
+ subtest:is(metrics.gc_blacknum, 0)
+end)
+
+test:test("gc-colors-stremty", function(subtest)
+ subtest:plan(1)
+ -- Empty string is specially recolored at the end
+ -- of atomic phase.
+ local strempty = ""
+ -- Check that none of the internal asserts will fail.
+ for i=1, 100 do
+ collectgarbage("collect")
+ end
+ subtest:ok(true, "no assertion failed")
+end)
+
+-- Upvalues are never gray.
+tbar_t = {}
+test:test("gc-colors-tbar", function(subtest)
+ subtest:plan(1)
+ local tabnew = require"table.new"
+ for i =1, 100 do
+ tbar_t.x = tabnew(i,0)
+ end
+ collectgarbage("collect")
+ tbar_t = nil
+ subtest:ok(true, "no assertion failed")
+end)
+
+test:test("gc-allocated-freed", function(subtest)
+ subtest:plan(1)
+
+ -- Force up garbage collect all dead objects.
+ collectgarbage("collect")
+
+ -- Bump getmetrincs table and string keys allocation.
+ local old_metrics = misc.getmetrics()
+
+ -- Remember allocated size for getmetrics table.
+ old_metrics = misc.getmetrics()
+
+ collectgarbage("collect")
+
+ local new_metrics = misc.getmetrics()
+
+ local diff_alloc = new_metrics.gc_allocated - old_metrics.gc_allocated
+ local getmetrics_alloc = diff_alloc
+
+ -- Do not use test:ok to avoid extra allocated/freed objects.
+ assert(getmetrics_alloc > 0, "count allocated table for getmetrics")
+ old_metrics = new_metrics
+
+ -- NB: Avoid operations that use internal global string buffer
+ -- (such as concatenation, string.format, table.concat)
+ -- while creating the string. Otherwise gc_freed/gc_allocated
+ -- relations will not be so straightforward.
+ local str = string.sub("Hello, world", 1, 5)
+ collectgarbage("collect")
+
+ new_metrics = misc.getmetrics()
+
+ diff_alloc = new_metrics.gc_allocated - old_metrics.gc_allocated
+ local diff_freed = new_metrics.gc_freed - old_metrics.gc_freed
+
+ assert(diff_alloc > getmetrics_alloc,
+ "allocated str 'Hello' and table for getmetrics")
+ assert(diff_freed == getmetrics_alloc,
+ "freed old old_metrics")
+ old_metrics = new_metrics
+
+ str = string.sub("Hello, world", 8, -1)
+
+ new_metrics = misc.getmetrics()
+
+ diff_alloc = new_metrics.gc_allocated - old_metrics.gc_allocated
+ diff_freed = new_metrics.gc_freed - old_metrics.gc_freed
+
+ assert(diff_alloc > getmetrics_alloc,
+ "allocated str 'world' and table for getmetrics")
+ assert(diff_freed == 0, "nothing to free without collectgarbage")
+ old_metrics = new_metrics
+ collectgarbage("collect")
+
+ new_metrics = misc.getmetrics()
+
+ diff_alloc = new_metrics.gc_allocated - old_metrics.gc_allocated
+ diff_freed = new_metrics.gc_freed - old_metrics.gc_freed
+
+ assert(diff_alloc == getmetrics_alloc,
+ "allocated last one table for getmetrics")
+ assert(diff_freed > 2 * getmetrics_alloc,
+ "freed str 'Hello' and 2 tables for getmetrics")
+ subtest:ok(true, "no assetion failed")
+end)
+
+test:test("gc-steps", function(subtest)
+ subtest:plan(24)
+
+ -- Some garbage has already created before the next line,
+ -- i.e. during fronted processing this chunk.
+ -- Let's put a full garbage collection cycle on top of that,
+ -- and confirm that non-null values are reported (we are not
+ -- yet interested in actual numbers):
+ collectgarbage("collect")
+ collectgarbage("stop")
+ local oldm = misc.getmetrics()
+ subtest:ok(oldm.gc_steps_pause > 0)
+ subtest:ok(oldm.gc_steps_propagate > 0)
+ subtest:ok(oldm.gc_steps_atomic > 0)
+ subtest:ok(oldm.gc_steps_sweepstring > 0)
+ subtest:ok(oldm.gc_steps_sweep > 0)
+ -- Nothing to finalize, skipped.
+ subtest:is(oldm.gc_steps_finalize, 0)
+
+ -- As long as we stopped the GC, consequent call
+ -- should return the same values:
+ local newm = misc.getmetrics()
+ subtest:is(newm.gc_steps_pause - oldm.gc_steps_pause, 0)
+ subtest:is(newm.gc_steps_propagate - oldm.gc_steps_propagate, 0)
+ subtest:is(newm.gc_steps_atomic - oldm.gc_steps_atomic, 0)
+ subtest:is(newm.gc_steps_sweepstring - oldm.gc_steps_sweepstring, 0)
+ subtest:is(newm.gc_steps_sweep - oldm.gc_steps_sweep, 0)
+ -- Nothing to finalize, skipped.
+ subtest:is(newm.gc_steps_finalize, 0)
+ oldm = newm
+
+ -- Now the last phase: run full GC once and make sure that
+ -- everything is being reported as expected:
+ collectgarbage("collect")
+ collectgarbage("stop")
+ newm = misc.getmetrics()
+ subtest:ok(newm.gc_steps_pause - oldm.gc_steps_pause == 1)
+ subtest:ok(newm.gc_steps_propagate - oldm.gc_steps_propagate >= 1)
+ subtest:ok(newm.gc_steps_atomic - oldm.gc_steps_atomic == 1)
+ subtest:ok(newm.gc_steps_sweepstring - oldm.gc_steps_sweepstring >= 1)
+ subtest:ok(newm.gc_steps_sweep - oldm.gc_steps_sweep >= 1)
+ -- Nothing to finalize, skipped.
+ subtest:is(newm.gc_steps_finalize, 0)
+ oldm = newm
+
+ -- Now let's run three GC cycles to ensure that zero-to-one
+ -- transition was not a lucky coincidence.
+ collectgarbage("collect")
+ collectgarbage("collect")
+ collectgarbage("collect")
+ collectgarbage("stop")
+ newm = misc.getmetrics()
+ subtest:ok(newm.gc_steps_pause - oldm.gc_steps_pause == 3)
+ subtest:ok(newm.gc_steps_propagate - oldm.gc_steps_propagate >= 3)
+ subtest:ok(newm.gc_steps_atomic - oldm.gc_steps_atomic == 3)
+ subtest:ok(newm.gc_steps_sweepstring - oldm.gc_steps_sweepstring >= 3)
+ subtest:ok(newm.gc_steps_sweep - oldm.gc_steps_sweep >= 3)
+ -- Nothing to finalize, skipped.
+ subtest:is(newm.gc_steps_finalize, 0)
+end)
+
+test:test("objcount", function(subtest)
+ subtest:plan(4)
+ local table_new = require("table.new")
+ local ffi = require("ffi")
+
+ jit.opt.start(0)
+
+ -- Remove all dead objects.
+ collectgarbage("collect")
+
+ -- Bump strings and table creation.
+ local old_metrics = misc.getmetrics()
+ old_metrics = misc.getmetrics()
+
+ local placeholder = {
+ str = {},
+ tab = {},
+ udata = {},
+ cdata = {},
+ }
+
+ -- Separate objects creations to separate jit traces.
+ for i = 1, 1000 do
+ table.insert(placeholder.str, tostring(i))
+ end
+
+ for i = 1, 1000 do
+ table.insert(placeholder.tab, table_new(i, 0))
+ end
+
+ for i = 1, 1000 do
+ table.insert(placeholder.udata, newproxy())
+ end
+
+ for i = 1, 1000 do
+ -- Check counting of VLA/VLS/aligned cdata.
+ table.insert(placeholder.cdata, ffi.new("char[?]", 4))
+ end
+
+ for i = 1, 1000 do
+ -- Check counting of non-VLA/VLS/aligned cdata.
+ table.insert(placeholder.cdata, ffi.new("uint64_t", i))
+ end
+
+ placeholder = nil
+ collectgarbage("collect")
+ local new_metrics = misc.getmetrics()
+
+ -- Check that amount of objects not increased.
+ subtest:is(new_metrics.gc_strnum, old_metrics.gc_strnum,
+ "strnum don't change")
+ subtest:is(new_metrics.gc_tabnum, old_metrics.gc_tabnum,
+ "tabnum don't change")
+ subtest:is(new_metrics.gc_udatanum, old_metrics.gc_udatanum,
+ "udatanum don't change")
+ subtest:is(new_metrics.gc_cdatanum, old_metrics.gc_cdatanum,
+ "cdatanum don't change")
+
+ -- Restore default jit settings.
+ jit.opt.start(jit_opt_default_level)
+end)
+
+test:test("snap-restores-direct-loop", function(subtest)
+ -- Compiled loop with a direct exit to the interpreter.
+ subtest:plan(1)
+
+ jit.opt.start(0, "hotloop=2")
+
+ local old_metrics = misc.getmetrics()
+
+ local sum = 0
+ for i = 1, 20 do
+ sum = sum + i
+ end
+
+ local new_metrics = misc.getmetrics()
+
+ -- A single snapshot restoration happened on loop finish:
+ subtest:is(new_metrics.jit_snap_restore - old_metrics.jit_snap_restore, 1)
+
+ -- Restore default jit settings.
+ jit.opt.start(jit_opt_default_level, "hotloop="..jit_opt_default_hotloop)
+end)
+
+test:test("snap-restores-loop-side-exit-non-compiled", function(subtest)
+ -- Compiled loop with a side exit which does not get compiled.
+ subtest:plan(1)
+
+ jit.opt.start(0, "hotloop=2", "hotexit=2", "minstitch=15")
+
+ local function foo(i)
+ -- math.fmod is not yet compiled!
+ return i <= 5 and i or math.fmod(i, 11)
+ end
+
+ local old_metrics = misc.getmetrics()
+ local sum = 0
+ for i = 1, 10 do
+ sum = sum + foo(i)
+ end
+
+ local new_metrics = misc.getmetrics()
+
+ -- Side exits from the root trace could not get compiled.
+ subtest:is(new_metrics.jit_snap_restore - old_metrics.jit_snap_restore, 5)
+
+ -- Restore default jit settings.
+ jit.opt.start(jit_opt_default_level, "hotloop="..jit_opt_default_hotloop,
+ "hotexit="..jit_opt_default_hotexit,
+ "minstitch="..jit_opt_default_minstitch)
+end)
+
+test:test("snap-restores-loop-side-exit", function(subtest)
+ -- Compiled loop with a side exit which gets compiled.
+ subtest:plan(1)
+
+ -- Optimization level is important here as `loop` optimization
+ -- may unroll the loop body and insert +1 side exit.
+ jit.opt.start(0, "hotloop=5", "hotexit=5")
+
+ local function foo(i)
+ return i <= 10 and i or tostring(i)
+ end
+
+ local old_metrics = misc.getmetrics()
+ local sum = 0
+ for i = 1, 20 do
+ sum = sum + foo(i)
+ end
+
+ local new_metrics = misc.getmetrics()
+
+ -- 5 side exits to the interpreter before trace gets hot
+ -- and compiled
+ -- 1 side exit on loop end
+ subtest:is(new_metrics.jit_snap_restore - old_metrics.jit_snap_restore, 6)
+
+ -- Restore default jit settings.
+ jit.opt.start(jit_opt_default_level, "hotloop="..jit_opt_default_hotloop,
+ "hotexit="..jit_opt_default_hotexit)
+end)
+
+test:test("snap-restores-scalar", function(subtest)
+ -- Compiled scalar trace with a direct exit to the interpreter.
+ subtest:plan(2)
+
+ -- For calls it will be 2 * hotloop (see lj_dispatch.{c,h}
+ -- and hotcall at vm_*.dasc).
+ jit.opt.start(3, "hotloop=2", "hotexit=3")
+
+ local function foo(i)
+ return i <= 15 and i or tostring(i)
+ end
+
+ local old_metrics = misc.getmetrics()
+
+ foo(1) -- interp only
+ foo(2) -- interp only
+ foo(3) -- interp only
+ foo(4) -- compile trace during this call
+ foo(5) -- follow the trace
+ foo(6) -- follow the trace
+ foo(7) -- follow the trace
+ foo(8) -- follow the trace
+ foo(9) -- follow the trace
+ foo(10) -- follow the trace
+
+ local new_metrics = misc.getmetrics()
+
+ -- No exits triggering snap restore so far: snapshot
+ -- restoration was inlined into the machine code.
+ subtest:is(new_metrics.jit_snap_restore - old_metrics.jit_snap_restore, 0)
+ old_metrics = new_metrics
+
+ -- Simply 2 side exits from the trace:
+ foo(20)
+ foo(21)
+
+ new_metrics = misc.getmetrics()
+ subtest:is(new_metrics.jit_snap_restore - old_metrics.jit_snap_restore, 2)
+
+ -- Restore default jit settings.
+ jit.opt.start(jit_opt_default_level, "hotloop="..jit_opt_default_hotloop,
+ "hotexit="..jit_opt_default_hotexit)
+end)
+
+test:test("strhash", function(subtest)
+ subtest:plan(1)
+
+ local old_metrics = misc.getmetrics()
+
+ local new_metrics = misc.getmetrics()
+ -- Do not use test:ok to avoid extra strhash hits/misses.
+ assert(new_metrics.strhash_hit - old_metrics.strhash_hit == 22)
+ assert(new_metrics.strhash_miss - old_metrics.strhash_miss == 0)
+ old_metrics = new_metrics
+
+ local str1 = "strhash".."_hit"
+
+ new_metrics = misc.getmetrics()
+ assert(new_metrics.strhash_hit - old_metrics.strhash_hit == 23)
+ assert(new_metrics.strhash_miss - old_metrics.strhash_miss == 0)
+ old_metrics = new_metrics
+
+ new_metrics = misc.getmetrics()
+ assert(new_metrics.strhash_hit - old_metrics.strhash_hit == 22)
+ assert(new_metrics.strhash_miss - old_metrics.strhash_miss == 0)
+ old_metrics = new_metrics
+
+ local str2 = "new".."string"
+
+ new_metrics = misc.getmetrics()
+ assert(new_metrics.strhash_hit - old_metrics.strhash_hit == 22)
+ assert(new_metrics.strhash_miss - old_metrics.strhash_miss == 1)
+ subtest:ok(true, "no assertion failed")
+end)
+
+test:test("tracenum-base", function(subtest)
+ subtest:plan(3)
+
+ jit.off()
+ jit.flush()
+ collectgarbage("collect")
+ local metrics = misc.getmetrics()
+ subtest:is(metrics.jit_trace_num, 0)
+
+ jit.on()
+ local sum = 0
+ for i = 1, 100 do
+ sum = sum + i
+ end
+
+ metrics = misc.getmetrics()
+ subtest:is(metrics.jit_trace_num, 1)
+
+ jit.off()
+ jit.flush()
+ collectgarbage("collect")
+
+ metrics = misc.getmetrics()
+ subtest:is(metrics.jit_trace_num, 0)
+
+ jit.on()
+end)
+
+os.exit(test:check() and 0 or 1)
--
2.28.0
More information about the Tarantool-patches
mailing list