From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from smtp54.i.mail.ru (smtp54.i.mail.ru [217.69.128.34]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by dev.tarantool.org (Postfix) with ESMTPS id EAF8A45C30C for ; Wed, 16 Dec 2020 22:14:44 +0300 (MSK) From: Sergey Kaplun Date: Wed, 16 Dec 2020 22:13:43 +0300 Message-Id: <80ca85106ba6a334a1b9ef40f6005bdb510c684d.1608142899.git.skaplun@tarantool.org> In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Subject: [Tarantool-patches] [PATCH luajit v1 08/11] profile: introduce memory profiler List-Id: Tarantool development patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: Igor Munkin , Sergey Ostanevich Cc: tarantool-patches@dev.tarantool.org This patch adds memory profile module. Lua and C API for it will be added at the next patches. When VM executes a trace, vmstate is set to the trace number. Also this patch defines special macro LJ_VMST_TRACE equaled to LJ_VMST__MAX to encapsulate all different traces into one vmstate when profiled. See for details. Part of tarantool/tarantool#5442 --- src/Makefile | 8 +- src/Makefile.dep | 4 + src/lj_arch.h | 22 ++ src/lj_obj.h | 8 + src/lj_state.c | 8 + src/lmisclib.h | 29 +++ src/profile/ljp_memprof.c | 413 ++++++++++++++++++++++++++++++++++++++ src/profile/ljp_memprof.h | 86 ++++++++ 8 files changed, 577 insertions(+), 1 deletion(-) create mode 100644 src/profile/ljp_memprof.c create mode 100644 src/profile/ljp_memprof.h diff --git a/src/Makefile b/src/Makefile index e00265c..1ade2ec 100644 --- a/src/Makefile +++ b/src/Makefile @@ -113,6 +113,12 @@ XCFLAGS= # Enable GC64 mode for x64. #XCFLAGS+= -DLUAJIT_ENABLE_GC64 # +# Disable the memory profiler. +#XCFLAGS+= -DLUAJIT_DISABLE_MEMPROF +# +# Disable the thread safe profiler. +#XCFLAGS+= -DLUAJIT_DISABLE_THREAD_SAFE +# ############################################################################## ############################################################################## @@ -469,7 +475,7 @@ DASM_FLAGS= $(DASM_XFLAGS) $(DASM_AFLAGS) DASM_DASC= vm_$(DASM_ARCH).dasc UTILS_O= utils/leb128.o -PROFILE_O= profile/ljp_write.o profile/ljp_symtab.o +PROFILE_O= profile/ljp_write.o profile/ljp_symtab.o profile/ljp_memprof.o BUILDVM_O= host/buildvm.o host/buildvm_asm.o host/buildvm_peobj.o \ host/buildvm_lib.o host/buildvm_fold.o BUILDVM_T= host/buildvm diff --git a/src/Makefile.dep b/src/Makefile.dep index 831a5ce..c41fdcf 100644 --- a/src/Makefile.dep +++ b/src/Makefile.dep @@ -248,6 +248,10 @@ host/buildvm_lib.o: host/buildvm_lib.c host/buildvm.h lj_def.h lua.h luaconf.h \ host/buildvm_peobj.o: host/buildvm_peobj.c host/buildvm.h lj_def.h lua.h \ luaconf.h lj_arch.h lj_bc.h lj_def.h lj_arch.h host/minilua.o: host/minilua.c +profile/ljp_memprof.o: profile/ljp_memprof.c profile/ljp_memprof.h lmisclib.h \ + lua.h luaconf.h lj_def.h lj_obj.h lj_arch.h lj_frame.h lj_bc.h lj_jit.h \ + lj_ir.h lj_gc.h profile/ljp_symtab.h lj_debug.h profile/ljp_symtab.h \ + profile/ljp_write.h profile/ljp_symtab.o: profile/ljp_symtab.c lj_obj.h lua.h luaconf.h lj_def.h \ lj_arch.h profile/ljp_write.h profile/ljp_symtab.h profile/ljp_write.o: profile/ljp_write.c profile/ljp_write.h utils/leb128.h \ diff --git a/src/lj_arch.h b/src/lj_arch.h index c8d7138..5967849 100644 --- a/src/lj_arch.h +++ b/src/lj_arch.h @@ -213,6 +213,8 @@ #define LJ_ARCH_VERSION 50 #endif +#define LJ_ARCH_NOMEMPROF 1 + #elif LUAJIT_TARGET == LUAJIT_ARCH_ARM64 #define LJ_ARCH_BITS 64 @@ -234,6 +236,8 @@ #define LJ_ARCH_VERSION 80 +#define LJ_ARCH_NOMEMPROF 1 + #elif LUAJIT_TARGET == LUAJIT_ARCH_PPC #ifndef LJ_ARCH_ENDIAN @@ -299,6 +303,8 @@ #define LJ_ARCH_XENON 1 #endif +#define LJ_ARCH_NOMEMPROF 1 + #elif LUAJIT_TARGET == LUAJIT_ARCH_MIPS32 || LUAJIT_TARGET == LUAJIT_ARCH_MIPS64 #if defined(__MIPSEL__) || defined(__MIPSEL) || defined(_MIPSEL) @@ -358,6 +364,8 @@ #define LJ_ARCH_VERSION 10 #endif +#define LJ_ARCH_NOMEMPROF 1 + #else #error "No target architecture defined" #endif @@ -564,4 +572,18 @@ #define LJ_52 0 #endif +/* Disable or enable the memory profiler. */ +#if defined(LUAJIT_DISABLE_MEMPROF) || defined(LJ_ARCH_NOMEMPROF) || LJ_TARGET_WINDOWS || LJ_TARGET_CYGWIN || LJ_TARGET_PS3 || LJ_TARGET_PS4 || LJ_TARGET_XBOX360 +#define LJ_HASMEMPROF 0 +#else +#define LJ_HASMEMPROF 1 +#endif + +/* Disable or enable the memory profiler's thread safety. */ +#if defined(LUAJIT_DISABLE_THREAD_SAFE) || LJ_TARGET_WINDOWS || LJ_TARGET_XBOX360 +#define LJ_THREAD_SAFE 0 +#else +#define LJ_THREAD_SAFE 1 +#endif + #endif diff --git a/src/lj_obj.h b/src/lj_obj.h index c94617d..c94b0bb 100644 --- a/src/lj_obj.h +++ b/src/lj_obj.h @@ -523,6 +523,14 @@ enum { LJ_VMST__MAX }; +/* +** PROFILER HACK: VM is inside a trace. This is a pseudo-state used by profiler. +** In fact, when VM executes a trace, vmstate is set to the trace number, but +** we aggregate all such cases into one VM state during per-VM state profiling. +*/ + +#define LJ_VMST_TRACE (LJ_VMST__MAX) + #define setvmstate(g, st) ((g)->vmstate = ~LJ_VMST_##st) /* Metamethods. ORDER MM */ diff --git a/src/lj_state.c b/src/lj_state.c index 1d9c628..09ac1b4 100644 --- a/src/lj_state.c +++ b/src/lj_state.c @@ -29,6 +29,10 @@ #include "lj_alloc.h" #include "luajit.h" +#if LJ_HASMEMPROF +#include "profile/ljp_memprof.h" +#endif + /* -- Stack handling ------------------------------------------------------ */ /* Stack sizes. */ @@ -243,6 +247,10 @@ LUA_API void lua_close(lua_State *L) global_State *g = G(L); int i; L = mainthread(g); /* Only the main thread can be closed. */ +#if LJ_HASMEMPROF + if (ljp_memprof_is_running()) + ljp_memprof_stop(); +#endif #if LJ_HASPROFILE luaJIT_profile_stop(L); #endif diff --git a/src/lmisclib.h b/src/lmisclib.h index 0c07707..b4c41da 100644 --- a/src/lmisclib.h +++ b/src/lmisclib.h @@ -60,6 +60,35 @@ struct luam_Metrics { LUAMISC_API void luaM_metrics(lua_State *L, struct luam_Metrics *metrics); +/* Profiler public API. */ +#define LUAM_PROFILE_SUCCESS 0 +#define LUAM_PROFILE_ERR 1 +#define LUAM_PROFILE_ERRRUN 2 +#define LUAM_PROFILE_ERRMEM 3 +#define LUAM_PROFILE_ERRIO 4 + +/* Profiler options. */ +struct luam_Prof_options { + /* Context for the profile writer and final callback. */ + void *ctx; + /* Custom buffer to write data. */ + uint8_t *buf; + /* The buffer's size. */ + size_t len; + /* + ** Writer function for profile events. + ** Should return amount of written bytes on success or zero in case of error. + ** Setting *data to NULL means end of profiling. + */ + size_t (*writer)(const void **data, size_t len, void *ctx); + /* + ** Callback on profiler stopping. Required for correctly cleaning + ** at vm shoutdown when profiler still running. + ** Returns zero on success. + */ + int (*on_stop)(void *ctx, uint8_t *buf); +}; + #define LUAM_MISCLIBNAME "misc" LUALIB_API int luaopen_misc(lua_State *L); diff --git a/src/profile/ljp_memprof.c b/src/profile/ljp_memprof.c new file mode 100644 index 0000000..2137d5a --- /dev/null +++ b/src/profile/ljp_memprof.c @@ -0,0 +1,413 @@ +/* +** Implementation of memory profiler. +** +** Major portions taken verbatim or adapted from the LuaVela. +** Copyright (C) 2015-2019 IPONWEB Ltd. +*/ + +#include + +#include "profile/ljp_memprof.h" +#include "lmisclib.h" +#include "lj_def.h" +#include "lj_arch.h" + +#if LJ_HASMEMPROF + +#if LJ_IS_THREAD_SAFE +#include +#endif + +#include "lua.h" + +#include "lj_obj.h" +#include "lj_frame.h" +#include "lj_debug.h" +#include "lj_gc.h" +#include "profile/ljp_symtab.h" +#include "profile/ljp_write.h" + +/* Allocation events: */ +#define AEVENT_ALLOC ((uint8_t)1) +#define AEVENT_FREE ((uint8_t)2) +#define AEVENT_REALLOC ((uint8_t)(AEVENT_ALLOC | AEVENT_FREE)) + +/* Allocation sources: */ +#define ASOURCE_INT ((uint8_t)(1 << 2)) +#define ASOURCE_LFUNC ((uint8_t)(2 << 2)) +#define ASOURCE_CFUNC ((uint8_t)(3 << 2)) + +/* Aux bits: */ + +/* +** Reserved. There is ~1 second between each two events marked with this flag. +** This will possibly be used later to implement dumps of the evolving heap. +*/ +#define LJM_TIMESTAMP ((uint8_t)(0x40)) + +#define LJM_EPILOGUE_HEADER 0x80 + +enum memprof_state { + /* memprof is not running. */ + MPS_IDLE, + /* memprof is running. */ + MPS_PROFILE, + /* + ** Stopped in case of stopped stream. + ** Saved errno is returned to user at memprof_stop. + */ + MPS_HALT +}; + +struct alloc { + lua_Alloc allocf; /* Allocating function. */ + void *state; /* Opaque allocator's state. */ +}; + +struct memprof { + global_State *g; /* Profiled VM. */ + enum memprof_state state; /* Internal state. */ + struct ljp_buffer out; /* Output accumulator. */ + struct alloc orig_alloc; /* Original allocator. */ + struct luam_Prof_options opt; /* Profiling options. */ + int saved_errno; /* Saved errno when profiler deinstrumented. */ +}; + +#if LJ_IS_THREAD_SAFE + +pthread_mutex_t memprof_mutex = PTHREAD_MUTEX_INITIALIZER; + +static LJ_AINLINE int memprof_lock(void) +{ + return pthread_mutex_lock(&memprof_mutex); +} + +static LJ_AINLINE int memprof_unlock(void) +{ + return pthread_mutex_unlock(&memprof_mutex); +} + +#else /* LJ_IS_THREAD_SAFE */ + +#define memprof_lock() +#define memprof_unlock() + +#endif /* LJ_IS_THREAD_SAFE */ + +static struct memprof memprof = {0}; + +const unsigned char ljm_header[] = {'l', 'j', 'm', LJM_CURRENT_FORMAT_VERSION, + 0x0, 0x0, 0x0}; + +static void memprof_write_lfunc(struct ljp_buffer *out, uint8_t header, + GCfunc *fn, struct lua_State *L, + cTValue *nextframe) +{ + const BCLine line = lj_debug_frameline(L, fn, nextframe); + ljp_write_byte(out, header | ASOURCE_LFUNC); + ljp_write_u64(out, (uintptr_t)funcproto(fn)); + ljp_write_u64(out, line >= 0 ? (uintptr_t)line : 0); +} + +static void memprof_write_cfunc(struct ljp_buffer *out, uint8_t header, + const GCfunc *fn) +{ + ljp_write_byte(out, header | ASOURCE_CFUNC); + ljp_write_u64(out, (uintptr_t)fn->c.f); +} + +static void memprof_write_ffunc(struct ljp_buffer *out, uint8_t header, + GCfunc *fn, struct lua_State *L, + cTValue *frame) +{ + cTValue *pframe = frame_prev(frame); + GCfunc *pfn = frame_func(pframe); + + /* + ** NB! If a fast function is called by a Lua function, report the + ** Lua function for more meaningful output. Otherwise report the fast + ** function as a C function. + */ + if (pfn != NULL && isluafunc(pfn)) + memprof_write_lfunc(out, header, pfn, L, frame); + else + memprof_write_cfunc(out, header, fn); +} + +static void memprof_write_func(struct memprof *mp, uint8_t header) +{ + struct ljp_buffer *out = &mp->out; + lua_State *L = gco2th(gcref(mp->g->mem_L)); + cTValue *frame = L->base - 1; + GCfunc *fn; + + fn = frame_func(frame); + + if (isluafunc(fn)) + memprof_write_lfunc(out, header, fn, L, NULL); + else if (isffunc(fn)) + memprof_write_ffunc(out, header, fn, L, frame); + else if (iscfunc(fn)) + memprof_write_cfunc(out, header, fn); + else + lua_assert(0); +} + +static void memprof_write_hvmstate(struct memprof *mp, uint8_t header) +{ + ljp_write_byte(&mp->out, header | ASOURCE_INT); +} + +/* +** NB! In ideal world, we should report allocations from traces as well. +** But since traces must follow the semantics of the original code, behaviour of +** Lua and JITted code must match 1:1 in terms of allocations, which makes +** using memprof with enabled JIT virtually redundant. Hence the stub below. +*/ +static void memprof_write_trace(struct memprof *mp, uint8_t header) +{ + ljp_write_byte(&mp->out, header | ASOURCE_INT); +} + +typedef void (*memprof_writer)(struct memprof *mp, uint8_t header); + +static const memprof_writer memprof_writers[] = { + memprof_write_hvmstate, /* LJ_VMST_INTERP */ + memprof_write_func, /* LJ_VMST_LFUNC */ + memprof_write_func, /* LJ_VMST_FFUNC */ + memprof_write_func, /* LJ_VMST_CFUNC */ + memprof_write_hvmstate, /* LJ_VMST_GC */ + memprof_write_hvmstate, /* LJ_VMST_EXIT */ + memprof_write_hvmstate, /* LJ_VMST_RECORD */ + memprof_write_hvmstate, /* LJ_VMST_OPT */ + memprof_write_hvmstate, /* LJ_VMST_ASM */ + memprof_write_trace /* LJ_VMST_TRACE */ +}; + +static void memprof_write_caller(struct memprof *mp, uint8_t aevent) +{ + const global_State *g = mp->g; + const uint32_t _vmstate = (uint32_t)~g->vmstate; + const uint32_t vmstate = _vmstate < LJ_VMST_TRACE ? _vmstate : LJ_VMST_TRACE; + const uint8_t header = aevent; + + memprof_writers[vmstate](mp, header); +} + +static int memprof_stop(const struct lua_State *L); + +static void *memprof_allocf(void *ud, void *ptr, size_t osize, size_t nsize) +{ + struct memprof *mp = &memprof; + struct alloc *oalloc = &mp->orig_alloc; + struct ljp_buffer *out = &mp->out; + void *nptr; + + lua_assert(MPS_PROFILE == mp->state); + lua_assert(oalloc->allocf != memprof_allocf); + lua_assert(oalloc->allocf != NULL); + lua_assert(ud == oalloc->state); + + nptr = oalloc->allocf(ud, ptr, osize, nsize); + + if (nsize == 0) { + memprof_write_caller(mp, AEVENT_FREE); + ljp_write_u64(out, (uintptr_t)ptr); + ljp_write_u64(out, (uint64_t)osize); + } else if (ptr == NULL) { + memprof_write_caller(mp, AEVENT_ALLOC); + ljp_write_u64(out, (uintptr_t)nptr); + ljp_write_u64(out, (uint64_t)nsize); + } else { + memprof_write_caller(mp, AEVENT_REALLOC); + ljp_write_u64(out, (uintptr_t)ptr); + ljp_write_u64(out, (uint64_t)osize); + ljp_write_u64(out, (uintptr_t)nptr); + ljp_write_u64(out, (uint64_t)nsize); + } + + /* Deinstrument memprof if required. */ + if (LJ_UNLIKELY(ljp_write_test_flag(out, STREAM_STOP))) + memprof_stop(NULL); + + return nptr; +} + +static void memprof_write_prologue(struct ljp_buffer *out) +{ + size_t i = 0; + const size_t len = sizeof(ljm_header) / sizeof(ljm_header[0]); + + for (; i < len; i++) + ljp_write_byte(out, ljm_header[i]); +} + +int ljp_memprof_start(struct lua_State *L, const struct luam_Prof_options *opt) +{ + struct memprof *mp = &memprof; + struct alloc *oalloc = &mp->orig_alloc; + + lua_assert(opt->writer != NULL && opt->on_stop != NULL); + lua_assert(opt->buf != NULL && opt->len != 0); + + memprof_lock(); + + if (mp->state != MPS_IDLE) { + memprof_unlock(); + return LUAM_PROFILE_ERRRUN; + } + + /* Discard possible old errno. */ + mp->saved_errno = 0; + + /* Init options: */ + memcpy(&mp->opt, opt, sizeof(*opt)); + + /* Init general fields: */ + mp->g = G(L); + mp->state = MPS_PROFILE; + + /* Init output: */ + ljp_write_init(&mp->out, mp->opt.writer, mp->opt.ctx, mp->opt.buf, + mp->opt.len); + ljp_symtab_write(&mp->out, mp->g); + memprof_write_prologue(&mp->out); + + if (LJ_UNLIKELY(ljp_write_test_flag(&mp->out, STREAM_ERR_IO) || + ljp_write_test_flag(&mp->out, STREAM_STOP))) { + /* on_stop call may change errno value. */ + int saved_errno = ljp_write_errno(&mp->out); + mp->opt.on_stop(mp->opt.ctx, mp->opt.buf); + ljp_write_terminate(&mp->out); + mp->state = MPS_IDLE; + memprof_unlock(); + errno = saved_errno; + return LUAM_PROFILE_ERRIO; + } + + /* Override allocating function: */ + oalloc->allocf = lua_getallocf(L, &oalloc->state); + lua_assert(oalloc->allocf != NULL); + lua_assert(oalloc->allocf != memprof_allocf); + lua_assert(oalloc->state != NULL); + lua_setallocf(L, memprof_allocf, oalloc->state); + + memprof_unlock(); + return LUAM_PROFILE_SUCCESS; +} + +static int memprof_stop(const struct lua_State *L) +{ + struct memprof *mp = &memprof; + struct alloc *oalloc = &mp->orig_alloc; + struct ljp_buffer *out = &mp->out; + int return_status = LUAM_PROFILE_SUCCESS; + int saved_errno = 0; + struct lua_State *main_L; + int cb_status; + + memprof_lock(); + + if (mp->state == MPS_HALT) { + errno = mp->saved_errno; + mp->state = MPS_IDLE + memprof_unlock(); + return LUAM_PROFILE_ERRIO; + } + + if (mp->state != MPS_PROFILE) { + memprof_unlock(); + return LUAM_PROFILE_ERRRUN; + } + + if (L != NULL && mp->g != G(L)) { + memprof_unlock(); + return LUAM_PROFILE_ERR; + } + + mp->state = MPS_IDLE; + + lua_assert(mp->g != NULL); + main_L = mainthread(mp->g); + + lua_assert(memprof_allocf == lua_getallocf(main_L, NULL)); + lua_assert(oalloc->allocf != NULL); + lua_assert(oalloc->state != NULL); + lua_setallocf(main_L, oalloc->allocf, oalloc->state); + + if (LJ_UNLIKELY(ljp_write_test_flag(out, STREAM_STOP))) { + lua_assert(ljp_write_test_flag(out, STREAM_ERR_IO)); + mp->state = MPS_HALT; + /* on_stop call may change errno value. */ + mp->saved_errno = ljp_write_errno(out); + /* Ignore possible errors. mp->opt.buf == NULL here. */ + mp->opt.on_stop(mp->opt.ctx, mp->opt.buf); + ljp_write_terminate(out); + memprof_unlock(); + return LUAM_PROFILE_ERRIO; + } + ljp_write_byte(out, LJM_EPILOGUE_HEADER); + + ljp_write_flush_buffer(out); + + cb_status = mp->opt.on_stop(mp->opt.ctx, mp->opt.buf); + if (LJ_UNLIKELY(ljp_write_test_flag(out, STREAM_ERR_IO) || cb_status != 0)) { + saved_errno = ljp_write_errno(out); + return_status = LUAM_PROFILE_ERRIO; + } + + ljp_write_terminate(out); + + memprof_unlock(); + errno = saved_errno; + return return_status; +} + +int ljp_memprof_stop(void) +{ + return memprof_stop(NULL); +} + +int ljp_memprof_stop_vm(const struct lua_State *L) +{ + return memprof_stop(L); +} + +int ljp_memprof_is_running(void) +{ + struct memprof *mp = &memprof; + int running; + + memprof_lock(); + running = mp->state == MPS_PROFILE; + memprof_unlock(); + + return running; +} + +#else /* LJ_HASMEMPROF */ + +int ljp_memprof_start(struct lua_State *L, const struct luam_Prof_options *opt) +{ + UNUSED(L); + UNUSED(opt); + return LUAM_PROFILE_ERR; +} + +int ljp_memprof_stop(void) +{ + return LUAM_PROFILE_ERR; +} + +int ljp_memprof_stop_vm(const struct lua_State *L) +{ + UNUSED(L); + return LUAM_PROFILE_ERR; +} + +int ljp_memprof_is_running(void) +{ + return 0; +} + +#endif /* LJ_HASMEMPROF */ diff --git a/src/profile/ljp_memprof.h b/src/profile/ljp_memprof.h new file mode 100644 index 0000000..90c1990 --- /dev/null +++ b/src/profile/ljp_memprof.h @@ -0,0 +1,86 @@ +/* +** Memory profiler. +** +** Major portions taken verbatim or adapted from the LuaVela. +** Copyright (C) 2015-2019 IPONWEB Ltd. +*/ + +#ifndef _LJP_MEMPROF_H +#define _LJP_MEMPROF_H + +/* +** Event stream format: +** +** stream := symtab memprof +** symtab := see +** memprof := prologue event* epilogue +** prologue := 'l' 'j' 'm' version reserved +** version := +** reserved := +** event := event-alloc | event-realloc | event-free +** event-alloc := event-header loc? naddr nsize +** event-realloc := event-header loc? oaddr osize naddr nsize +** event-free := event-header loc? oaddr osize +** event-header := +** loc := loc-lua | loc-c +** loc-lua := sym-addr line-no +** loc-c := sym-addr +** sym-addr := +** line-no := +** oaddr := +** naddr := +** osize := +** nsize := +** epilogue := event-header +** +** : A single byte (no surprises here) +** : Unsigned integer represented in ULEB128 encoding +** +** (Order of bits below is hi -> lo) +** +** version: [VVVVVVVV] +** * VVVVVVVV: Byte interpreted as a plain integer version number +** +** event-header: [FTUUSSEE] +** * EE : 2 bits for representing allocation event type (AEVENT_*) +** * SS : 2 bits for representing allocation source type (ASOURCE_*) +** * UU : 2 unused bits +** * T : Reserved. 0 for regular events, 1 for the events marked with +** the timestamp mark. It is assumed that the time distance between +** two marked events is approximately the same and is equal +** to 1 second. Always zero for now. +** * F : 0 for regular events, 1 for epilogue's *F*inal header +** (if F is set to 1, all other bits are currently ignored) +*/ + +struct lua_State; + +#define LJM_CURRENT_FORMAT_VERSION 0x02 + +struct luam_Prof_options; + +/* +** Starts profiling. Returns LUAM_PROFILE_SUCCESS on success and one of +** LUAM_PROFILE_ERR* codes otherwise. Destroyer is called in case of +** LUAM_PROFILE_ERRIO. +*/ +int ljp_memprof_start(struct lua_State *L, const struct luam_Prof_options *opt); + +/* +** Stops profiling. Returns LUAM_PROFILE_SUCCESS on success and one of +** LUAM_PROFILE_ERR* codes otherwise. If writer() function returns zero +** on call at buffer flush, profiled stream stops, or on_stop() callback +** returns non-zero value, returns LUAM_PROFILE_ERRIO. +*/ +int ljp_memprof_stop(void); + +/* +** VM g is currently being profiled, behaves exactly as ljp_memprof_stop(). +** Otherwise does nothing and returns LUAM_PROFILE_ERR. +*/ +int ljp_memprof_stop_vm(const struct lua_State *L); + +/* Check that profiler is running. */ +int ljp_memprof_is_running(void); + +#endif -- 2.28.0