From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from [87.239.111.99] (localhost [127.0.0.1]) by dev.tarantool.org (Postfix) with ESMTP id 634526ECE8; Wed, 20 Apr 2022 16:00:13 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org 634526ECE8 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=tarantool.org; s=dev; t=1650459613; bh=vz2VXn75rhXA0Mu05LZsTcUDBLn6JhTYljOnhAXf6jg=; h=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe: List-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To: From; b=cFcNulr7lfzKknDxtZm8FeQJQc8anYfBcyLXaIa8VOqWrmWob8LfjGDOv/nlkiDqw RSUlYFU7BXKseUNm9Uud+oXInZoFgds9I7ooEJOcKcaicSCj/vUF7c4YZS4buKdEZG yRK1Mj2p92i9djH1PiLfE+EQ7gGINrZSewD+LU2Q= Received: from mail-lf1-f43.google.com (mail-lf1-f43.google.com [209.85.167.43]) (using TLSv1.3 with cipher TLS_AES_128_GCM_SHA256 (128/128 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by dev.tarantool.org (Postfix) with ESMTPS id 5DD306F177 for ; Wed, 20 Apr 2022 15:58:17 +0300 (MSK) DKIM-Filter: OpenDKIM Filter v2.11.0 dev.tarantool.org 5DD306F177 Received: by mail-lf1-f43.google.com with SMTP id y32so2837091lfa.6 for ; Wed, 20 Apr 2022 05:58:17 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=/bOQxMk8d4MukXzmbVTpjjn9p4RiXrj4JHStLg6IjXY=; b=E/XBZk2/HQGPab0MC98d6grU+wmEvrlM3Q/4MhLg3cRBd0MfW+DUJWjgonOw2liv/p IKTl5bQ7FyMq+XgbVZ3cDglfVvSNJe+ww00n77whfnvzHvDjS5JsPDj4IoUdILO2SkLS 3Hsv4L2u7p6ALCYkGAE3NRdkV/MxbLn2TtSpCqivlR+Dwwu95YI3SxevXAYXPfNGV/SW ixbAlqDf6RkHiQe8S96TM4cWzOfj0pKTjsXyf5zR+ZpHLetbtCrZ7UTS+Gbhz/Axyq/f T5LtZ9AEpm639HiP8pGO7mxsgFSkVT/RxkMFC82noyqpjbP7ImnbJqhuXbQzwCVIU80/ iLyw== X-Gm-Message-State: AOAM533zNtRIpBb4aN7HOpw0VzY2HvlkBZhGMUTYHUnWRwYCsJMGPAdF 5Q2TgHmwjK0nGH4bfXA1mxY9pErrY2VHeA== X-Google-Smtp-Source: ABdhPJydybq1TLtXna5ior5ArpApyI6baS0O7p4BbVM5Ns8as1PmRU/aBdvTn94xYvNPcgQ4JQ8xSw== X-Received: by 2002:a19:da12:0:b0:46b:926f:d34b with SMTP id r18-20020a19da12000000b0046b926fd34bmr14548731lfg.646.1650459496217; Wed, 20 Apr 2022 05:58:16 -0700 (PDT) Received: from localhost.localdomain ([93.175.28.57]) by smtp.gmail.com with ESMTPSA id o3-20020a198c03000000b00448b7b1780csm1824076lfd.63.2022.04.20.05.58.15 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 20 Apr 2022 05:58:15 -0700 (PDT) X-Google-Original-From: Maxim Kokryashkin To: tarantool-patches@dev.tarantool.org, imun@tarantool.org, skaplun@tarantool.org Date: Wed, 20 Apr 2022 15:58:00 +0300 Message-Id: X-Mailer: git-send-email 2.35.1 In-Reply-To: References: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Subject: [Tarantool-patches] [PATCH luajit v5 4/7] core: introduce lua and platform profiler X-BeenThere: tarantool-patches@dev.tarantool.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: Tarantool development patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , From: Maxim Kokryashkin via Tarantool-patches Reply-To: Maxim Kokryashkin Errors-To: tarantool-patches-bounces@dev.tarantool.org Sender: "Tarantool-patches" From: Mikhail Shishatskiy This patch introduces a sampling platform profiler for the Lua machine. The profiler uses the signal sampling backend from the low-level profiler built in vanilla LuaJIT. Thus, one cannot use both profilers at the same time. First of all profiler dumps the definitions of all loaded Lua functions and all loaded shared libraries (symtab) via the write buffer. When a signal occurs, the profiler dumps current VM state and additional info about about stacks that can be considered consistent in a given state. Also, profiling can be done without writing to file: vmstate counters are accumulated in static memory and can be received via the `luaM_sysprof_report` function. For more details, see the header file. When profiling is over, the old signal handler is restored, and a special epilogue header is written. Part of tarantool/tarantool#781 --- CMakeLists.txt | 6 + cmake/SetTargetFlags.cmake | 2 +- src/CMakeLists.txt | 1 + src/Makefile.dep.original | 12 +- src/Makefile.original | 4 +- src/lj_arch.h | 11 + src/lj_errmsg.h | 2 +- src/lj_mapi.c | 26 + src/lj_memprof.h | 8 +- src/lj_state.c | 7 + src/lj_sysprof.c | 528 ++++++++++++++++++ src/lj_sysprof.h | 93 +++ src/lmisclib.h | 94 ++++ test/tarantool-tests/CMakeLists.txt | 1 + .../misclib-sysprof-capi.test.lua | 54 ++ .../misclib-sysprof-capi/CMakeLists.txt | 1 + .../misclib-sysprof-capi/testsysprof.c | 271 +++++++++ 17 files changed, 1107 insertions(+), 14 deletions(-) create mode 100644 src/lj_sysprof.c create mode 100644 src/lj_sysprof.h create mode 100644 test/tarantool-tests/misclib-sysprof-capi.test.lua create mode 100644 test/tarantool-tests/misclib-sysprof-capi/CMakeLists.txt create mode 100644 test/tarantool-tests/misclib-sysprof-capi/testsysprof.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 5348e043..659ee879 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -184,6 +184,12 @@ if(LUAJIT_DISABLE_MEMPROF) AppendFlags(TARGET_C_FLAGS -DLUAJIT_DISABLE_MEMPROF) endif() +# Disable platform and Lua profiler. +option(LUAJIT_DISABLE_SYSPROF "LuaJIT platform and Lua profiler support" OFF) +if(LUAJIT_DISABLE_SYSPROF) + AppendFlags(TARGET_C_FLAGS -DLUAJIT_DISABLE_SYSPROF) +endif() + # Switch to harder (and slower) hash function when a collision # chain in the string hash table exceeds a certain length. option(LUAJIT_SMART_STRINGS "Harder string hashing function" ON) diff --git a/cmake/SetTargetFlags.cmake b/cmake/SetTargetFlags.cmake index a6e86463..b544d2ac 100644 --- a/cmake/SetTargetFlags.cmake +++ b/cmake/SetTargetFlags.cmake @@ -21,7 +21,7 @@ LuaJITArch(LUAJIT_ARCH "${TESTARCH}") # distribute the binaries to a different machine you could also # use: -march=native. if(LUAJIT_ARCH STREQUAL "x86") - AppendFlags(TARGET_C_FLAGS -march=i686 -msse -msse2 -mfpmath=sse) + AppendFlags(TARGET_C_FLAGS -march=i686 -msse -msse2 -mfpmath=sse -fno-omit-frame-pointer) endif() if(CMAKE_SYSTEM_NAME STREQUAL "Darwin") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b4ce407b..1a3f106a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -71,6 +71,7 @@ make_source_list(SOURCES_PROFILER lj_profile.c lj_profile_timer.c lj_symtab.c + lj_sysprof.c ) # Lua standard library + extensions by LuaJIT. diff --git a/src/Makefile.dep.original b/src/Makefile.dep.original index efe39e84..8a5b649b 100644 --- a/src/Makefile.dep.original +++ b/src/Makefile.dep.original @@ -140,8 +140,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_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_sysprof.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 @@ -194,7 +194,9 @@ lj_snap.o: lj_snap.c lj_obj.h lua.h luaconf.h lj_def.h lj_arch.h lj_gc.h \ lj_state.o: lj_state.c lj_obj.h lua.h luaconf.h lj_def.h lj_arch.h \ lj_gc.h lj_err.h lj_errmsg.h lj_buf.h lj_str.h lj_tab.h lj_func.h \ lj_meta.h lj_state.h lj_frame.h lj_bc.h lj_ctype.h lj_trace.h lj_jit.h \ - lj_ir.h lj_dispatch.h lj_traceerr.h lj_vm.h lj_lex.h lj_alloc.h luajit.h + lj_ir.h lj_dispatch.h lj_traceerr.h lj_vm.h lj_lex.h lj_alloc.h luajit.h \ + lj_memprof.h lj_wbuf.h lmisclib.h lj_debug.h lj_strfmt.h lj_char.h \ + lj_symtab.h lj_sysprof.h lj_profile_timer.h lj_str.o: lj_str.c lj_obj.h lua.h luaconf.h lj_def.h lj_arch.h lj_gc.h \ lj_err.h lj_errmsg.h lj_str.h lj_char.h lj_strfmt.o: lj_strfmt.c lj_obj.h lua.h luaconf.h lj_def.h lj_arch.h \ @@ -205,6 +207,10 @@ lj_strscan.o: lj_strscan.c lj_obj.h lua.h luaconf.h lj_def.h lj_arch.h \ lj_char.h lj_strscan.h lj_symtab.o: lj_symtab.c lj_symtab.h lj_wbuf.h lj_def.h lua.h luaconf.h \ lj_obj.h lj_arch.h +lj_sysprof.o: lj_sysprof.c lj_arch.h lua.h luaconf.h lj_sysprof.h \ + lj_obj.h lj_def.h lmisclib.h lj_debug.h lj_dispatch.h lj_bc.h lj_jit.h \ + lj_ir.h lj_frame.h lj_trace.h lj_traceerr.h lj_wbuf.h lj_profile_timer.h \ + lj_symtab.h lj_tab.o: lj_tab.c lj_obj.h lua.h luaconf.h lj_def.h lj_arch.h lj_gc.h \ lj_err.h lj_errmsg.h lj_tab.h lj_trace.o: lj_trace.c lj_obj.h lua.h luaconf.h lj_def.h lj_arch.h \ diff --git a/src/Makefile.original b/src/Makefile.original index fe37caa7..50b84bfe 100644 --- a/src/Makefile.original +++ b/src/Makefile.original @@ -491,8 +491,8 @@ LJCORE_O= lj_gc.o lj_err.o lj_char.o lj_bc.o lj_obj.o lj_buf.o lj_wbuf.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_mapi.o lj_profile.o lj_profile_timer.o \ - lj_memprof.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_memprof.o lj_sysprof.o lj_lex.o lj_parse.o lj_bcread.o lj_bcwrite.obj \ + 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 \ lj_mcode.o lj_snap.o lj_record.o lj_crecord.o lj_ffrecord.o \ lj_asm.o lj_trace.o lj_gdbjit.o \ diff --git a/src/lj_arch.h b/src/lj_arch.h index 5bf0afb8..19e6413c 100644 --- a/src/lj_arch.h +++ b/src/lj_arch.h @@ -220,6 +220,7 @@ #endif #define LJ_ARCH_NOMEMPROF 1 +#define LJ_ARCH_NOSYSPROF 1 #elif LUAJIT_TARGET == LUAJIT_ARCH_ARM64 @@ -243,6 +244,7 @@ #define LJ_ARCH_VERSION 80 #define LJ_ARCH_NOMEMPROF 1 +#define LJ_ARCH_NOSYSPROF 1 #elif LUAJIT_TARGET == LUAJIT_ARCH_PPC @@ -310,6 +312,7 @@ #endif #define LJ_ARCH_NOMEMPROF 1 +#define LJ_ARCH_NOSYSPROF 1 #elif LUAJIT_TARGET == LUAJIT_ARCH_MIPS32 || LUAJIT_TARGET == LUAJIT_ARCH_MIPS64 @@ -371,6 +374,7 @@ #endif #define LJ_ARCH_NOMEMPROF 1 +#define LJ_ARCH_NOSYSPROF 1 #else #error "No target architecture defined" @@ -585,4 +589,11 @@ #define LJ_HASMEMPROF 1 #endif +/* Disable or enable the platform and Lua profiler. */ +#if defined(LUAJIT_DISABLE_SYSPROF) || defined(LJ_ARCH_NOSYSPROF) || LJ_TARGET_WINDOWS || LJ_TARGET_CYGWIN || LJ_TARGET_OSX || LJ_TARGET_PS3 || LJ_TARGET_PS4 || LJ_TARGET_XBOX360 +#define LJ_HASSYSPROF 0 +#else +#define LJ_HASSYSPROF 1 +#endif + #endif diff --git a/src/lj_errmsg.h b/src/lj_errmsg.h index ae0a18c0..77a08cb0 100644 --- a/src/lj_errmsg.h +++ b/src/lj_errmsg.h @@ -187,7 +187,7 @@ ERRDEF(FFI_NYICALL, "NYI: cannot call this C function (yet)") /* Profiler errors. */ ERRDEF(PROF_MISUSE, "profiler misuse") -#if LJ_HASMEMPROF +#if LJ_HASMEMPROF || LJ_HASSYSPROF ERRDEF(PROF_ISRUNNING, "profiler is running already") ERRDEF(PROF_NOTRUNNING, "profiler is not running") #endif diff --git a/src/lj_mapi.c b/src/lj_mapi.c index b2b35a17..73b5dc74 100644 --- a/src/lj_mapi.c +++ b/src/lj_mapi.c @@ -18,6 +18,8 @@ #include "lj_jit.h" #endif +#include "lj_sysprof.h" + LUAMISC_API void luaM_metrics(lua_State *L, struct luam_Metrics *metrics) { global_State *g = G(L); @@ -63,3 +65,27 @@ LUAMISC_API void luaM_metrics(lua_State *L, struct luam_Metrics *metrics) metrics->jit_trace_num = 0; #endif } + +/* --- Platform and Lua profiler ------------------------------------------ */ + +LUAMISC_API int luaM_sysprof_configure(const struct luam_Sysprof_Config *config) +{ + return lj_sysprof_configure(config); +} + +LUAMISC_API int luaM_sysprof_start(lua_State *L, + const struct luam_Sysprof_Options *opt) +{ + return lj_sysprof_start(L, opt); +} + +/* Stop profiling. */ +LUAMISC_API int luaM_sysprof_stop(lua_State *L) +{ + return lj_sysprof_stop(L); +} + +LUAMISC_API int luaM_sysprof_report(struct luam_Sysprof_Counters *counters) +{ + return lj_sysprof_report(counters); +} diff --git a/src/lj_memprof.h b/src/lj_memprof.h index 7fe17af4..d604a2af 100644 --- a/src/lj_memprof.h +++ b/src/lj_memprof.h @@ -15,6 +15,7 @@ #include "lj_def.h" #include "lj_wbuf.h" +#include "lmisclib.h" #define LJM_CURRENT_FORMAT_VERSION 0x02 @@ -75,13 +76,6 @@ #define LJM_EPILOGUE_HEADER 0x80 -/* Profiler public API. */ -#define PROFILE_SUCCESS 0 -#define PROFILE_ERRUSE 1 -#define PROFILE_ERRRUN 2 -#define PROFILE_ERRMEM 3 -#define PROFILE_ERRIO 4 - /* Profiler options. */ struct lj_memprof_options { /* Context for the profile writer and final callback. */ diff --git a/src/lj_state.c b/src/lj_state.c index f82b1b5b..cc6f92f1 100644 --- a/src/lj_state.c +++ b/src/lj_state.c @@ -33,6 +33,10 @@ #include "lj_memprof.h" #endif +#if LJ_HASSYSPROF +#include "lj_sysprof.h" +#endif + /* -- Stack handling ------------------------------------------------------ */ /* Stack sizes. */ @@ -267,6 +271,9 @@ LUA_API void lua_close(lua_State *L) #if LJ_HASMEMPROF lj_memprof_stop(L); #endif +#if LJ_HASSYSPROF + lj_sysprof_stop(L); +#endif #if LJ_HASPROFILE luaJIT_profile_stop(L); #endif diff --git a/src/lj_sysprof.c b/src/lj_sysprof.c new file mode 100644 index 00000000..9d7bc64f --- /dev/null +++ b/src/lj_sysprof.c @@ -0,0 +1,528 @@ +/* +** Implementation of sysprof - platform and Lua profiler. +*/ + +#define lj_sysprof_c +#define LUA_CORE + +#include "lj_arch.h" +#include "lj_sysprof.h" + +#if LJ_HASSYSPROF + +#include "lj_obj.h" +#include "lj_debug.h" +#include "lj_dispatch.h" +#include "lj_frame.h" + +#if LJ_HASJIT +#include "lj_jit.h" +#include "lj_trace.h" +#endif + +#include "lj_wbuf.h" +#include "lj_profile_timer.h" +#include "lj_symtab.h" + +#include +#include + +/* Check that `backtrace` is available. */ +#if __has_include() + #include + #define HAS_BACKTRACE 1 +#else + #define HAS_BACKTRACE 0 +#endif + +/* +** Number of profiler frames we need to omit during stack +** unwinding. +** +------------------------+ +** 0 | stream_frame_host | +** +------------------------+ +** 1 | default_backtrace_host | +** +------------------------+ +** 2 | stream_backtrace_host | +** +------------------------+ +** 3 | stream_{guest/host} | +** +------------------------+ +** 4 | stream_event | +** +------------------------+ +** 5 | sysprof_record_sample | +** +------------------------+ +** 6 | sysprof_signal_handler | +** +------------------------+ +*/ +#define SYSPROF_HANDLER_STACK_DEPTH 7 +#define SYSPROF_BACKTRACE_FRAME_MAX 512 + +/* Check that vmstate fits in 4 bits (see streaming format) */ +#define vmstfit4(st) ((st & ~(uint32_t)((1 << 4) - 1)) == 0) + +enum sysprof_state { + /* Profiler needs to be configured. */ + SPS_UNCONFIGURED, + /* Profiler is not running. */ + SPS_IDLE, + /* Profiler is running. */ + SPS_PROFILE, + /* + ** Stopped in case of stopped or failed stream. + ** Saved errno is set at luaM_sysprof_stop. + */ + SPS_HALT +}; + +struct sysprof { + global_State *g; /* Profiled VM. */ + pthread_t thread; /* Profiled thread. */ + volatile sig_atomic_t state; /* Internal state. */ + struct lj_wbuf out; /* Output accumulator. */ + struct luam_Sysprof_Counters counters; /* Profiling counters. */ + struct luam_Sysprof_Options opt; /* Profiling options. */ + struct luam_Sysprof_Config config; /* Profiler configurations. */ + lj_profile_timer timer; /* Profiling timer. */ + int saved_errno; /* Saved errno when profiler failed. */ +}; +/* +** XXX: Only one VM can be profiled at a time. +*/ + +static struct sysprof sysprof = {0}; + +/* --- Stream ------------------------------------------------------------- */ + +static const uint8_t ljp_header[] = {'l', 'j', 'p', LJP_FORMAT_VERSION, + 0x0, 0x0, 0x0}; + +static int stream_is_needed(struct sysprof *sp) +{ + return sp->opt.mode != LUAM_SYSPROF_DEFAULT; +} + +static void stream_prologue(struct sysprof *sp) +{ + lj_symtab_dump(&sp->out, sp->g); + lj_wbuf_addn(&sp->out, ljp_header, sizeof(ljp_header)); +} + +static void stream_epilogue(struct sysprof *sp) +{ + lj_wbuf_addbyte(&sp->out, LJP_EPILOGUE_BYTE); +} + +static void stream_lfunc(struct lj_wbuf *buf, const GCfunc *func) +{ + lua_assert(isluafunc(func)); + const GCproto *pt = funcproto(func); + lua_assert(pt != NULL); + lj_wbuf_addbyte(buf, LJP_FRAME_LFUNC); + lj_wbuf_addu64(buf, (uintptr_t)pt); + lj_wbuf_addu64(buf, (uint64_t)pt->firstline); +} + +static void stream_cfunc(struct lj_wbuf *buf, const GCfunc *func) +{ + lua_assert(iscfunc(func)); + lj_wbuf_addbyte(buf, LJP_FRAME_CFUNC); + lj_wbuf_addu64(buf, (uintptr_t)func->c.f); +} + +static void stream_ffunc(struct lj_wbuf *buf, const GCfunc *func) +{ + lua_assert(isffunc(func)); + lj_wbuf_addbyte(buf, LJP_FRAME_FFUNC); + lj_wbuf_addu64(buf, func->c.ffid); +} + +static void stream_frame_lua(struct lj_wbuf *buf, const cTValue *frame) +{ + const GCfunc *func = frame_func(frame); + lua_assert(func != NULL); + if (isluafunc(func)) + stream_lfunc(buf, func); + else if (isffunc(func)) + stream_ffunc(buf, func); + else if (iscfunc(func)) + stream_cfunc(buf, func); + else + /* Unreachable. */ + lua_assert(0); +} + +static void stream_backtrace_lua(struct sysprof *sp) +{ + global_State *g = sp->g; + struct lj_wbuf *buf = &sp->out; + cTValue *top_frame = NULL, *frame = NULL, *bot = NULL; + lua_State *L = NULL; + + lua_assert(g != NULL); + L = gco2th(gcref(g->cur_L)); + lua_assert(L != NULL); + + top_frame = g->top_frame - 1; //(1 + LJ_FR2) + + bot = tvref(L->stack) + LJ_FR2; + /* Traverse frames backwards */ + for (frame = top_frame; frame > bot; frame = frame_prev(frame)) { + if (frame_gc(frame) == obj2gco(L) || frame_isvarg(frame)) + continue; /* Skip dummy frames. See lj_err_optype_call(). */ + stream_frame_lua(buf, frame); + } + + lj_wbuf_addbyte(buf, LJP_FRAME_LUA_LAST); +} + +static void *stream_frame_host(int frame_no, void *addr) +{ + struct sysprof *sp = &sysprof; + /* + ** We don't want the profiler stack to be streamed, as it will + ** burden the profile with unnecessary information. + */ + if (LJ_UNLIKELY(frame_no <= SYSPROF_HANDLER_STACK_DEPTH)) + return addr; + else if (LJ_UNLIKELY(sp->opt.mode == LUAM_SYSPROF_LEAF && + frame_no > SYSPROF_HANDLER_STACK_DEPTH)) + return NULL; + + lj_wbuf_addu64(&sp->out, (uintptr_t)addr); + return addr; +} + +static void default_backtrace_host(void *(writer)(int frame_no, void *addr)) +{ + static void *backtrace_buf[SYSPROF_BACKTRACE_FRAME_MAX] = {}; + + struct sysprof *sp = &sysprof; + int max_depth = sp->opt.mode == LUAM_SYSPROF_LEAF + ? SYSPROF_HANDLER_STACK_DEPTH + 1 + : SYSPROF_BACKTRACE_FRAME_MAX; + const int depth = backtrace(backtrace_buf, max_depth); + lua_assert(depth <= max_depth); + + for (int i = SYSPROF_HANDLER_STACK_DEPTH; i < depth; ++i) { + if (!writer(i - SYSPROF_HANDLER_STACK_DEPTH + 1, backtrace_buf[i])) + return; + } +} + +static void stream_backtrace_host(struct sysprof *sp) +{ + lua_assert(sp->config.backtracer != NULL); + sp->config.backtracer(stream_frame_host); + lj_wbuf_addu64(&sp->out, (uintptr_t)LJP_FRAME_HOST_LAST); +} + +#if LJ_HASJIT +static void stream_trace(struct sysprof *sp) +{ + struct lj_wbuf *out = &sp->out; + uint32_t traceno = sp->g->vmstate; + jit_State *J = G2J(sp->g); + GCtrace *trace = traceref(J, traceno); + + GCproto *startpt = gco2pt(gcref(trace->startpt)); + + lj_wbuf_addu64(out, traceno); + lj_wbuf_addu64(out, (uintptr_t)startpt); + lj_wbuf_addu64(out, startpt->firstline); +} +#endif + +static void stream_guest(struct sysprof *sp) +{ + stream_backtrace_lua(sp); + stream_backtrace_host(sp); +} + +static void stream_host(struct sysprof *sp) +{ + stream_backtrace_host(sp); +} + +typedef void (*event_streamer)(struct sysprof *sp); + +static event_streamer event_streamers[] = { + /* XXX: order is important */ + stream_host, /* LJ_VMST_INTERP */ + stream_guest, /* LJ_VMST_LFUNC */ + stream_guest, /* LJ_VMST_FFUNC */ + stream_guest, /* LJ_VMST_CFUNC */ + stream_host, /* LJ_VMST_GC */ + stream_host, /* LJ_VMST_EXIT */ + stream_host, /* LJ_VMST_RECORD */ + stream_host, /* LJ_VMST_OPT */ + stream_host, /* LJ_VMST_ASM */ +#if LJ_HASJIT + stream_trace /* LJ_VMST_TRACE */ +#endif +}; + +static void stream_event(struct sysprof *sp, uint32_t vmstate) +{ + event_streamer stream = NULL; + + lua_assert(vmstfit4(vmstate)); + lj_wbuf_addbyte(&sp->out, (uint8_t)vmstate); + stream = event_streamers[vmstate]; + lua_assert(NULL != stream); + stream(sp); +} + +/* -- Signal handler ------------------------------------------------------ */ + +static void sysprof_record_sample(struct sysprof *sp, siginfo_t *info) +{ + global_State *g = sp->g; + uint32_t _vmstate = ~(uint32_t)(g->vmstate); + uint32_t vmstate = _vmstate < LJ_VMST_TRACE ? _vmstate : LJ_VMST_TRACE; + + lua_assert(pthread_self() == sp->thread); + + /* Caveat: order of counters must match vmstate order in . */ + ((uint64_t *)&sp->counters)[vmstate]++; + + sp->counters.samples++; + + if (!stream_is_needed(sp)) + return; + + stream_event(sp, vmstate); + if (LJ_UNLIKELY(lj_wbuf_test_flag(&sp->out, STREAM_ERRIO|STREAM_STOP))) { + sp->saved_errno = lj_wbuf_errno(&sp->out); + lj_wbuf_terminate(&sp->out); + sp->state = SPS_HALT; + } +} + +static void sysprof_signal_handler(int sig, siginfo_t *info, void *ctx) +{ + struct sysprof *sp = &sysprof; + UNUSED(sig); + UNUSED(ctx); + + switch (sp->state) { + case SPS_PROFILE: + sysprof_record_sample(sp, info); + break; + + case SPS_IDLE: + case SPS_HALT: + /* noop */ + break; + + default: + lua_assert(0); + break; + } +} + +/* -- Internal ------------------------------------------------------------ */ + +static int sysprof_validate(struct sysprof *sp, + const struct luam_Sysprof_Options *opt) +{ + switch (sp->state) { + case SPS_UNCONFIGURED: + return PROFILE_ERRUSE; + + case SPS_IDLE: + if (opt->mode > LUAM_SYSPROF_CALLGRAPH) { + return PROFILE_ERRUSE; + } else if (opt->mode != LUAM_SYSPROF_DEFAULT && + (opt->buf == NULL || opt->len == 0 || + sp->config.writer == NULL || sp->config.on_stop == NULL)) { + return PROFILE_ERRUSE; + } else if (opt->interval == 0) { + return PROFILE_ERRUSE; + } + break; + + case SPS_PROFILE: + case SPS_HALT: + return PROFILE_ERRRUN; + + default: + lua_assert(0); + break; + } + + return PROFILE_SUCCESS; +} + +static int sysprof_init(struct sysprof *sp, lua_State *L, + const struct luam_Sysprof_Options *opt) +{ + const int status = sysprof_validate(sp, opt); + if (PROFILE_SUCCESS != status) + return status; + + /* Copy validated options to sysprof state. */ + memcpy(&sp->opt, opt, sizeof(sp->opt)); + + /* Init general fields. */ + sp->g = G(L); + sp->thread = pthread_self(); + + /* Reset counters. */ + memset(&sp->counters, 0, sizeof(sp->counters)); + + /* Reset saved errno. */ + sp->saved_errno = 0; + + if (stream_is_needed(sp)) + lj_wbuf_init(&sp->out, sp->config.writer, opt->ctx, opt->buf, opt->len); + + return PROFILE_SUCCESS; +} + +/* -- Public profiling API ------------------------------------------------ */ + +int lj_sysprof_configure(const struct luam_Sysprof_Config *config) +{ + struct sysprof *sp = &sysprof; + lua_assert(config != NULL); + if (sp->state != SPS_UNCONFIGURED && sp->state != SPS_IDLE) + return PROFILE_ERRUSE; + + memcpy(&sp->config, config, sizeof(*config)); + + if (sp->config.backtracer == NULL) { + if (HAS_BACKTRACE == 0) + return PROFILE_ERRUSE; + sp->config.backtracer = default_backtrace_host; + /* + ** XXX: `backtrace` is not signal-safe, according to man, + ** because it is lazy loaded on the first call, which triggers + ** allocations. We need to call `backtrace` before starting profiling + ** to avoid lazy loading. + */ + void *dummy = NULL; + backtrace(&dummy, 1); + } + + sp->state = SPS_IDLE; + + return PROFILE_SUCCESS; +} + +int lj_sysprof_start(lua_State *L, const struct luam_Sysprof_Options *opt) +{ + struct sysprof *sp = &sysprof; + + int status = sysprof_init(sp, L, opt); + if (PROFILE_SUCCESS != status) { + if (NULL != sp->config.on_stop) { + /* + ** Initialization may fail in case of unconfigured sysprof, + ** so we cannot guarantee cleaning up resources in this case. + */ + sp->config.on_stop(opt->ctx, opt->buf); + } + return status; + } + + sp->state = SPS_PROFILE; + + if (stream_is_needed(sp)) { + stream_prologue(sp); + if (LJ_UNLIKELY(lj_wbuf_test_flag(&sp->out, STREAM_ERRIO|STREAM_STOP))) { + /* on_stop call may change errno value. */ + const int saved_errno = lj_wbuf_errno(&sp->out); + /* Ignore possible errors. mp->out.buf may be NULL here. */ + sp->config.on_stop(opt->ctx, sp->out.buf); + lj_wbuf_terminate(&sp->out); + sp->state = SPS_IDLE; + errno = saved_errno; + return PROFILE_ERRIO; + } + } + + sp->timer.opt.interval_msec = opt->interval; + sp->timer.opt.handler = sysprof_signal_handler; + lj_profile_timer_start(&sp->timer); + + return PROFILE_SUCCESS; +} + +int lj_sysprof_stop(lua_State *L) +{ + struct sysprof *sp = &sysprof; + global_State *g = sp->g; + struct lj_wbuf *out = &sp->out; + + if (SPS_IDLE == sp->state) + return PROFILE_ERRRUN; + else if (G(L) != g) + return PROFILE_ERRUSE; + + lj_profile_timer_stop(&sp->timer); + + if (SPS_HALT == sp->state) { + errno = sp->saved_errno; + sp->state = SPS_IDLE; + /* wbuf was terminated when error occured. */ + return PROFILE_ERRIO; + } + + sp->state = SPS_IDLE; + + if (stream_is_needed(sp)) { + int cb_status = 0; + + stream_epilogue(sp); + lj_wbuf_flush(out); + + cb_status = sp->config.on_stop(sp->opt.ctx, out->buf); + if (LJ_UNLIKELY(lj_wbuf_test_flag(out, STREAM_ERRIO | STREAM_STOP)) || + cb_status != 0) { + errno = lj_wbuf_errno(out); + lj_wbuf_terminate(out); + return PROFILE_ERRIO; + } + + lj_wbuf_terminate(out); + } + + return PROFILE_SUCCESS; +} + +int lj_sysprof_report(struct luam_Sysprof_Counters *counters) +{ + const struct sysprof *sp = &sysprof; + if (sp->state != SPS_IDLE) + return PROFILE_ERRUSE; + memcpy(counters, &sp->counters, sizeof(sp->counters)); + return PROFILE_SUCCESS; +} + +#else /* LJ_HASSYSPROF */ + +int lj_sysprof_configure(const struct luam_Sysprof_Config *config) +{ + UNUSED(config); + return PROFILE_ERRUSE; +} + +int lj_sysprof_start(lua_State *L, const struct luam_Sysprof_Options *opt) +{ + UNUSED(L); + return PROFILE_ERRUSE; +} + +int lj_sysprof_stop(lua_State *L) +{ + UNUSED(L); + return PROFILE_ERRUSE; +} + +int lj_sysprof_report(struct luam_Sysprof_Counters *counters) +{ + UNUSED(counters); + return PROFILE_ERRUSE; +} + +#endif /* LJ_HASSYSPROF */ diff --git a/src/lj_sysprof.h b/src/lj_sysprof.h new file mode 100644 index 00000000..6fe5eea6 --- /dev/null +++ b/src/lj_sysprof.h @@ -0,0 +1,93 @@ +/* +** Sysprof - platform and Lua profiler. +*/ + +/* +** XXX: Platform profiler is not thread safe. Please, don't try to +** use it inside several VM, you can profile only one at a time. +*/ + +/* +** XXX: Platform profiler uses the same signal backend as lj_profile. Please, +** don't use both at the same time. +*/ + +#ifndef _LJ_SYSPROF_H +#define _LJ_SYSPROF_H + +#include "lj_obj.h" +#include "lmisclib.h" + +#define LJP_FORMAT_VERSION 0x1 + +/* +** Event stream format: +** +** stream := symtab sysprof +** symtab := see symtab description +** sysprof := prologue sample* epilogue +** prologue := 'l' 'j' 'p' version reserved +** version := +** reserved := +** sample := sample-guest | sample-host | sample-trace +** sample-guest := sample-header stack-lua stack-host +** sample-host := sample-header stack-host +** sample-trace := sample-header traceno sym-addr line-no +** sample-header := +** stack-lua := frame-lua* frame-lua-last +** stack-host := frame-host* frame-host-last +** frame-lua := frame-lfunc | frame-cfunc | frame-ffunc +** frame-lfunc := frame-header sym-addr line-no +** frame-cfunc := frame-header exec-addr +** frame-ffunc := frame-header ffid +** frame-lua-last := frame-header +** frame-header := +** frame-host := exec-addr +** frame-host-last := +** line-no := +** traceno := +** ffid := +** sym-addr := +** exec-addr := +** epilogue := sample-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 +** +** sample-header: [FUUUUEEE] +** * EEE : 3 bits for representing vmstate (LJ_VMST_*) +** * UUUU : 4 unused bits +** * F : 0 for regular samples, 1 for epilogue's Final header +** (if F is set to 1, all other bits are currently ignored) +** +** frame-header: [FUUUUUEE] +** * EE : 2 bits for representing frame type (FRAME_*) +** * UUUUU : 5 unused bits +** * F : 0 for regular frames, 1 for final frame +** (if F is set to 1, all other bits are currently ignored) +** +** frame-host-last = NULL +*/ + +#define LJP_FRAME_LFUNC ((uint8_t)1) +#define LJP_FRAME_CFUNC ((uint8_t)2) +#define LJP_FRAME_FFUNC ((uint8_t)3) +#define LJP_FRAME_LUA_LAST 0x80 +#define LJP_FRAME_HOST_LAST NULL + +#define LJP_EPILOGUE_BYTE 0x80 + +int lj_sysprof_configure(const struct luam_Sysprof_Config *config); + +int lj_sysprof_start(lua_State *L, const struct luam_Sysprof_Options *opt); + +int lj_sysprof_stop(lua_State *L); + +int lj_sysprof_report(struct luam_Sysprof_Counters *counters); + +#endif diff --git a/src/lmisclib.h b/src/lmisclib.h index 0c07707e..b41f7f59 100644 --- a/src/lmisclib.h +++ b/src/lmisclib.h @@ -60,6 +60,100 @@ struct luam_Metrics { LUAMISC_API void luaM_metrics(lua_State *L, struct luam_Metrics *metrics); +/* --- Sysprof - platform and lua profiler -------------------------------- */ + +/* Profiler configurations. */ +struct luam_Sysprof_Config { + /* + ** Writer function for profile events. Must be async-safe, see also + ** `man 7 signal-safety`. + ** Should return amount of written bytes on success or zero in case of error. + ** Setting *data to NULL means end of profiling. + ** For details see . + */ + size_t (*writer)(const void **data, size_t len, void *ctx); + /* + ** Callback on profiler stopping. Required for correctly cleaning + ** at VM finalization when profiler is still running. + ** Returns zero on success. + */ + int (*on_stop)(void *ctx, uint8_t *buf); + /* + ** Backtracing function for the host stack. Should call `frame_writer` on + ** each frame in the stack in the order from the stack top to the stack + ** bottom. The `frame_writer` function is implemented inside the sysprof + ** and will be passed to the `backtracer` function. If `frame_writer` returns + ** NULL, backtracing should be stopped. If `frame_writer` returns not NULL, + ** the backtracing should be continued if there are frames left. + */ + void (*backtracer)(void *(*frame_writer)(int frame_no, void *addr)); +}; + +/* +** DEFAULT mode collects only data for luam_sysprof_counters, which is stored +** in memory and can be collected with luaM_sysprof_report after profiler +** stops. +*/ +#define LUAM_SYSPROF_DEFAULT 0 +/* +** LEAF mode = DEFAULT + streams samples with only top frames of host and +** guests stacks in format described in +*/ +#define LUAM_SYSPROF_LEAF 1 +/* +** CALLGRAPH mode = DEFAULT + streams samples with full callchains of host +** and guest stacks in format described in +*/ +#define LUAM_SYSPROF_CALLGRAPH 2 + +struct luam_Sysprof_Counters { + uint64_t vmst_interp; + uint64_t vmst_lfunc; + uint64_t vmst_ffunc; + uint64_t vmst_cfunc; + uint64_t vmst_gc; + uint64_t vmst_exit; + uint64_t vmst_record; + uint64_t vmst_opt; + uint64_t vmst_asm; + uint64_t vmst_trace; + /* + ** XXX: Order of vmst counters is important: it should be the same as the + ** order of the vmstates. + */ + uint64_t samples; +}; + +/* Profiler options. */ +struct luam_Sysprof_Options { + /* Profiling mode. */ + uint8_t mode; + /* Sampling interval in msec. */ + uint64_t interval; + /* Custom buffer to write data. */ + uint8_t *buf; + /* The buffer's size. */ + size_t len; + /* Context for the profile writer and final callback. */ + void *ctx; +}; + +#define PROFILE_SUCCESS 0 +#define PROFILE_ERRUSE 1 +#define PROFILE_ERRRUN 2 +#define PROFILE_ERRMEM 3 +#define PROFILE_ERRIO 4 + +LUAMISC_API int luaM_sysprof_configure(const struct luam_Sysprof_Config *config); + +LUAMISC_API int luaM_sysprof_start(lua_State *L, + const struct luam_Sysprof_Options *opt); + +LUAMISC_API int luaM_sysprof_stop(lua_State *L); + +LUAMISC_API int luaM_sysprof_report(struct luam_Sysprof_Counters *counters); + + #define LUAM_MISCLIBNAME "misc" LUALIB_API int luaopen_misc(lua_State *L); diff --git a/test/tarantool-tests/CMakeLists.txt b/test/tarantool-tests/CMakeLists.txt index b21500a0..9435b667 100644 --- a/test/tarantool-tests/CMakeLists.txt +++ b/test/tarantool-tests/CMakeLists.txt @@ -63,6 +63,7 @@ add_subdirectory(lj-49-bad-lightuserdata) add_subdirectory(lj-601-fix-gc-finderrfunc) add_subdirectory(lj-flush-on-trace) add_subdirectory(misclib-getmetrics-capi) +add_subdirectory(misclib-sysprof-capi) # The part of the memory profiler toolchain is located in tools # directory, jit, profiler, and bytecode toolchains are located diff --git a/test/tarantool-tests/misclib-sysprof-capi.test.lua b/test/tarantool-tests/misclib-sysprof-capi.test.lua new file mode 100644 index 00000000..9d15d6d5 --- /dev/null +++ b/test/tarantool-tests/misclib-sysprof-capi.test.lua @@ -0,0 +1,54 @@ +-- Sysprof is implemented for x86 and x64 architectures only. +require("utils").skipcond( + (jit.arch ~= "x86" and jit.arch ~= "x64") + or jit.os == "OSX", + jit.arch.." architecture or "..jit.os.. + " OS is NIY for sysprof" +) + +local testsysprof = require("testsysprof") + +local tap = require("tap") +local jit = require('jit') + +jit.off() + +local test = tap.test("clib-misc-sysprof") +test:plan(4) + +test:ok(testsysprof.base()) +test:ok(testsysprof.validation()) + +local function lua_payload(n) + if n <= 1 then + return n + end + return lua_payload(n - 1) + lua_payload(n - 2) +end + +local function payload() + local n_iterations = 500000 + + local co = coroutine.create(function () + for i = 1, n_iterations do + if i % 2 == 0 then + testsysprof.c_payload(10) + else + lua_payload(10) + end + coroutine.yield() + end + end) + + for _ = 1, n_iterations do + coroutine.resume(co) + end +end + +test:ok(testsysprof.profile_func(payload)) + +jit.on() +jit.flush() + +test:ok(testsysprof.profile_func(payload)) +os.exit(test:check() and 0 or 1) diff --git a/test/tarantool-tests/misclib-sysprof-capi/CMakeLists.txt b/test/tarantool-tests/misclib-sysprof-capi/CMakeLists.txt new file mode 100644 index 00000000..d9fb1a1a --- /dev/null +++ b/test/tarantool-tests/misclib-sysprof-capi/CMakeLists.txt @@ -0,0 +1 @@ +BuildTestCLib(testsysprof testsysprof.c) diff --git a/test/tarantool-tests/misclib-sysprof-capi/testsysprof.c b/test/tarantool-tests/misclib-sysprof-capi/testsysprof.c new file mode 100644 index 00000000..4b258766 --- /dev/null +++ b/test/tarantool-tests/misclib-sysprof-capi/testsysprof.c @@ -0,0 +1,271 @@ +#include +#include +#include + +#include + +#include +#include +#include +#include + +#undef NDEBUG +#include + +#define UNUSED(x) ((void)(x)) + +/* --- utils -------------------------------------------------------------- */ + +#define SYSPROF_INTERVAL_DEFAULT 11 + +/* + ** Yep, 8Mb. Tuned in order not to bother the platform with too often flushes. + */ +#define STREAM_BUFFER_SIZE (8 * 1024 * 1024) + +/* Structure given as ctx to sysprof writer and on_stop callback. */ +struct sysprof_ctx { + /* Output file descriptor for data. */ + int fd; + /* Buffer for data. */ + uint8_t buf[STREAM_BUFFER_SIZE]; +}; + +/* + ** Default buffer writer function. + ** Just call fwrite to the corresponding FILE. + */ +static size_t buffer_writer_default(const void **buf_addr, size_t len, + void *opt) +{ + struct sysprof_ctx *ctx = opt; + int fd = ctx->fd; + const void * const buf_start = *buf_addr; + const void *data = *buf_addr; + size_t write_total = 0; + + assert(len <= STREAM_BUFFER_SIZE); + + for (;;) { + const size_t written = write(fd, data, len - write_total); + + if (written == 0) { + /* Re-tries write in case of EINTR. */ + if (errno != EINTR) { + /* Will be freed as whole chunk later. */ + *buf_addr = NULL; + return write_total; + } + errno = 0; + continue; + } + + write_total += written; + assert(write_total <= len); + + if (write_total == len) + break; + + data = (uint8_t *)data + (ptrdiff_t)written; + } + + *buf_addr = buf_start; + return write_total; +} + +/* Default on stop callback. Just close the corresponding stream. */ +static int on_stop_cb_default(void *opt, uint8_t *buf) +{ + UNUSED(buf); + struct sysprof_ctx *ctx = opt; + int fd = ctx->fd; + free(ctx); + return close(fd); +} + +static int stream_init(struct luam_Sysprof_Options *opt) +{ + struct sysprof_ctx *ctx = calloc(1, sizeof(struct sysprof_ctx)); + if (NULL == ctx) + return PROFILE_ERRIO; + + ctx->fd = open("/dev/null", O_WRONLY | O_CREAT, 0644); + if (-1 == ctx->fd) { + free(ctx); + return PROFILE_ERRIO; + } + + opt->ctx = ctx; + opt->buf = ctx->buf; + opt->len = STREAM_BUFFER_SIZE; + + return PROFILE_SUCCESS; +} + +/* --- Payload ------------------------------------------------------------ */ + +static double fib(double n) +{ + if (n <= 1) + return n; + return fib(n - 1) + fib(n - 2); +} + +static int c_payload(lua_State *L) +{ + fib(luaL_checknumber(L, 1)); + lua_pushboolean(L, 1); + return 1; +} + +/* --- sysprof C API tests ------------------------------------------------ */ + +static int base(lua_State *L) +{ + struct luam_Sysprof_Config config = {}; + struct luam_Sysprof_Options opt = {}; + struct luam_Sysprof_Counters cnt = {}; + + (void)config.writer; + (void)config.on_stop; + (void)config.backtracer; + + (void)opt.interval; + (void)opt.mode; + (void)opt.ctx; + (void)opt.buf; + (void)opt.len; + + luaM_sysprof_report(&cnt); + + (void)cnt.samples; + (void)cnt.vmst_interp; + (void)cnt.vmst_lfunc; + (void)cnt.vmst_ffunc; + (void)cnt.vmst_cfunc; + (void)cnt.vmst_gc; + (void)cnt.vmst_exit; + (void)cnt.vmst_record; + (void)cnt.vmst_opt; + (void)cnt.vmst_asm; + (void)cnt.vmst_trace; + + lua_pushboolean(L, 1); + return 1; +} + +static int validation(lua_State *L) +{ + struct luam_Sysprof_Config config = {}; + struct luam_Sysprof_Options opt = {}; + int status = PROFILE_SUCCESS; + + status = luaM_sysprof_configure(&config); + assert(PROFILE_SUCCESS == status); + + /* Unknown mode. */ + opt.mode = 0x40; + status = luaM_sysprof_start(L, &opt); + assert(PROFILE_ERRUSE == status); + + /* Buffer not configured. */ + opt.mode = LUAM_SYSPROF_CALLGRAPH; + opt.buf = NULL; + status = luaM_sysprof_start(L, &opt); + assert(PROFILE_ERRUSE == status); + + /* Bad interval. */ + opt.mode = LUAM_SYSPROF_DEFAULT; + opt.interval = 0; + status = luaM_sysprof_start(L, &opt); + assert(PROFILE_ERRUSE == status); + + /* Check if profiling started. */ + opt.mode = LUAM_SYSPROF_DEFAULT; + opt.interval = SYSPROF_INTERVAL_DEFAULT; + status = luaM_sysprof_start(L, &opt); + assert(PROFILE_SUCCESS == status); + + /* Already running. */ + status = luaM_sysprof_start(L, &opt); + assert(PROFILE_ERRRUN == status); + + /* Profiler stopping. */ + status = luaM_sysprof_stop(L); + assert(PROFILE_SUCCESS == status); + + /* Stopping profiler which is not running. */ + status = luaM_sysprof_stop(L); + assert(PROFILE_ERRRUN == status); + + lua_pushboolean(L, 1); + return 1; +} + +static int profile_func(lua_State *L) +{ + struct luam_Sysprof_Config config = {}; + struct luam_Sysprof_Options opt = {}; + struct luam_Sysprof_Counters cnt = {}; + int status = PROFILE_ERRUSE; + + int n = lua_gettop(L); + if (n != 1 || !lua_isfunction(L, 1)) + luaL_error(L, "incorrect argument: 1 function is required"); + + /* + ** Since all the other modes functionality is the + ** subset of CALLGRAPH mode, run this mode to test + ** the profiler's behavior. + */ + opt.mode = LUAM_SYSPROF_CALLGRAPH; + opt.interval = SYSPROF_INTERVAL_DEFAULT; + stream_init(&opt); + + config.on_stop = on_stop_cb_default; + config.writer = buffer_writer_default; + status = luaM_sysprof_configure(&config); + assert(PROFILE_SUCCESS == status); + + status = luaM_sysprof_start(L, &opt); + assert(PROFILE_SUCCESS == status); + + /* Run payload. */ + if (lua_pcall(L, 0, 0, 0) != LUA_OK) + luaL_error(L, "error running payload: %s", lua_tostring(L, -1)); + + status = luaM_sysprof_stop(L); + assert(PROFILE_SUCCESS == status); + + status = luaM_sysprof_report(&cnt); + assert(PROFILE_SUCCESS == status); + + assert(cnt.samples > 1); + assert(cnt.samples == cnt.vmst_asm + + cnt.vmst_cfunc + + cnt.vmst_exit + + cnt.vmst_ffunc + + cnt.vmst_gc + + cnt.vmst_interp + + cnt.vmst_lfunc + + cnt.vmst_opt + + cnt.vmst_record + + cnt.vmst_trace); + + lua_pushboolean(L, 1); + return 1; +} + +static const struct luaL_Reg testsysprof[] = { + {"base", base}, + {"c_payload", c_payload}, + {"profile_func", profile_func}, + {"validation", validation}, + {NULL, NULL} +}; + +LUA_API int luaopen_testsysprof(lua_State *L) +{ + luaL_register(L, "testsysprof", testsysprof); + return 1; +} -- 2.35.1