Update Lua profiler; add ability to run OpenMW with old LuaJit that doesn't allow custom allocator (Lua profiler will be disabled in this case)

7098-improve-post-process-behavior-with-transparent-objects
Petr Mikheev 2 years ago
parent 02a9069a0e
commit 55db95d4cf

@ -20,8 +20,9 @@ namespace MWLua
saveLuaBinaryData(esm, event.mEventData);
}
void loadEvents(sol::state& lua, ESM::ESMReader& esm, GlobalEventQueue& globalEvents, LocalEventQueue& localEvents,
const std::map<int, int>& contentFileMapping, const LuaUtil::UserdataSerializer* serializer)
void loadEvents(sol::state_view& lua, ESM::ESMReader& esm, GlobalEventQueue& globalEvents,
LocalEventQueue& localEvents, const std::map<int, int>& contentFileMapping,
const LuaUtil::UserdataSerializer* serializer)
{
while (esm.isNextSub("LUAE"))
{

@ -35,7 +35,7 @@ namespace MWLua
using GlobalEventQueue = std::vector<GlobalEvent>;
using LocalEventQueue = std::vector<LocalEvent>;
void loadEvents(sol::state& lua, ESM::ESMReader& esm, GlobalEventQueue&, LocalEventQueue&,
void loadEvents(sol::state_view& lua, ESM::ESMReader& esm, GlobalEventQueue&, LocalEventQueue&,
const std::map<int, int>& contentFileMapping, const LuaUtil::UserdataSerializer* serializer);
void saveEvents(ESM::ESMWriter& esm, const GlobalEventQueue&, const LocalEventQueue&);
}

@ -39,6 +39,8 @@ namespace MWLua
static LuaUtil::LuaStateSettings createLuaStateSettings()
{
if (!Settings::Manager::getBool("lua profiler", "Lua"))
LuaUtil::LuaState::disableProfiler();
return { .mInstructionLimit = Settings::Manager::getUInt64("instruction limit per call", "Lua"),
.mMemoryLimit = Settings::Manager::getUInt64("memory limit", "Lua"),
.mSmallAllocMaxSize = Settings::Manager::getUInt64("small alloc max size", "Lua"),
@ -147,9 +149,9 @@ namespace MWLua
mWorldView.update();
mGlobalScripts.CPUusageNextFrame();
mGlobalScripts.statsNextFrame();
for (LocalScripts* scripts : mActiveLocalScripts)
scripts->CPUusageNextFrame();
scripts->statsNextFrame();
std::vector<GlobalEvent> globalEvents = std::move(mGlobalEvents);
std::vector<LocalEvent> localEvents = std::move(mLocalEvents);
@ -620,17 +622,39 @@ namespace MWLua
std::string LuaManager::formatResourceUsageStats() const
{
if (!LuaUtil::LuaState::isProfilerEnabled())
return "Lua profiler is disabled";
std::stringstream out;
constexpr int nameW = 50;
constexpr int valueW = 12;
auto outMemSize = [&](int64_t bytes) {
constexpr int64_t limit = 10000;
out << std::right << std::setw(valueW - 3);
if (bytes < limit)
out << bytes << " B ";
else if (bytes < limit * 1024)
out << (bytes / 1024) << " KB";
else if (bytes < limit * 1024 * 1024)
out << (bytes / (1024 * 1024)) << " MB";
else
out << (bytes / (1024 * 1024 * 1024)) << " GB";
};
static const uint64_t smallAllocSize = Settings::Manager::getUInt64("small alloc max size", "Lua");
out << "Total memory usage: " << mLua.getTotalMemoryUsage() << "\n";
out << "Total memory usage:";
outMemSize(mLua.getTotalMemoryUsage());
out << "\n";
out << "small alloc max size = " << smallAllocSize << " (section [Lua] in settings.cfg)\n";
out << "Smaller values give more information for the profiler, but increase performance overhead.\n";
out << " Memory allocations <= " << smallAllocSize << " bytes: " << mLua.getSmallAllocMemoryUsage()
<< " (not tracked)\n";
out << " Memory allocations > " << smallAllocSize
<< " bytes: " << mLua.getTotalMemoryUsage() - mLua.getSmallAllocMemoryUsage() << " (see the table below)\n";
out << "\n";
out << " Memory allocations <= " << smallAllocSize << " bytes:";
outMemSize(mLua.getSmallAllocMemoryUsage());
out << " (not tracked)\n";
out << " Memory allocations > " << smallAllocSize << " bytes:";
outMemSize(mLua.getTotalMemoryUsage() - mLua.getSmallAllocMemoryUsage());
out << " (see the table below)\n\n";
using Stats = LuaUtil::ScriptsContainer::ScriptStats;
@ -653,16 +677,22 @@ namespace MWLua
out << "No selected object. Use the in-game console to select an object for detailed profile.\n";
out << "\n";
constexpr int nameW = 50;
constexpr int valueW = 12;
out << "Legend\n";
out << " ops: Averaged number of Lua instruction per frame;\n";
out << " memory: Aggregated size of Lua allocations > " << smallAllocSize << " bytes;\n";
out << " [all]: Sum over all instances of each script;\n";
out << " [active]: Sum over all active (i.e. currently in scene) instances of each script;\n";
out << " [inactive]: Sum over all inactive instances of each script;\n";
out << " [for selected object]: Only for the object that is selected in the console;\n";
out << "\n";
out << std::left;
out << " " << std::setw(nameW + 2) << "*** Resource usage per script";
out << std::right;
out << std::setw(valueW) << "CPU";
out << std::setw(valueW) << "ops";
out << std::setw(valueW) << "memory";
out << std::setw(valueW) << "memory";
out << std::setw(valueW) << "CPU";
out << std::setw(valueW) << "ops";
out << std::setw(valueW) << "memory";
out << "\n";
out << std::left << " " << std::setw(nameW + 2) << "[name]" << std::right;
@ -681,19 +711,24 @@ namespace MWLua
if (mConfiguration[i].mScriptPath.size() > nameW)
out << "\n " << std::setw(nameW) << ""; // if path is too long, break line
out << std::right;
out << std::setw(valueW) << static_cast<int64_t>(activeStats[i].mCPUusage);
out << std::setw(valueW) << activeStats[i].mMemoryUsage;
out << std::setw(valueW) << mLua.getMemoryUsageByScriptIndex(i) - activeStats[i].mMemoryUsage;
out << std::setw(valueW) << static_cast<int64_t>(activeStats[i].mAvgInstructionCount);
outMemSize(activeStats[i].mMemoryUsage);
outMemSize(mLua.getMemoryUsageByScriptIndex(i) - activeStats[i].mMemoryUsage);
if (isGlobal)
out << std::setw(valueW * 2) << "NA (global script)";
else if (selectedPtr.isEmpty())
out << std::setw(valueW * 2) << "NA (not selected) ";
else if (!selectedScripts || !selectedScripts->hasScript(i))
out << std::setw(valueW) << "-" << std::setw(valueW) << selectedStats[i].mMemoryUsage;
{
out << std::setw(valueW) << "-";
outMemSize(selectedStats[i].mMemoryUsage);
}
else
out << std::setw(valueW) << static_cast<int64_t>(selectedStats[i].mCPUusage) << std::setw(valueW)
<< selectedStats[i].mMemoryUsage;
{
out << std::setw(valueW) << static_cast<int64_t>(selectedStats[i].mAvgInstructionCount);
outMemSize(selectedStats[i].mMemoryUsage);
}
out << "\n";
}

@ -146,7 +146,7 @@ namespace MWLua
void registerObjectList(const std::string& prefix, const Context& context)
{
using ListT = ObjectList<ObjectT>;
sol::state& lua = context.mLua->sol();
sol::state_view& lua = context.mLua->sol();
ObjectRegistry* registry = context.mWorldView->getObjectRegistry();
sol::usertype<ListT> listT = lua.new_usertype<ListT>(prefix + "ObjectList");
listT[sol::meta_function::to_string]

@ -15,7 +15,7 @@ namespace
using namespace TestingOpenMW;
template <typename T>
T get(sol::state& lua, const std::string& luaCode)
T get(sol::state_view& lua, const std::string& luaCode)
{
return lua.safe_script("return " + luaCode).get<T>();
}
@ -83,7 +83,7 @@ you_have_arrows: "Arrows count: {count}"
TEST_F(LuaL10nTest, L10n)
{
LuaUtil::LuaState lua{ mVFS.get(), &mCfg };
sol::state& l = lua.sol();
sol::state_view& l = lua.sol();
internal::CaptureStdout();
l10n::Manager l10nManager(mVFS.get());
l10nManager.setPreferredLocales({ "de", "en" });
@ -164,5 +164,4 @@ you_have_arrows: "Arrows count: {count}"
l10nManager.setPreferredLocales({ "en" });
EXPECT_EQ(get<std::string>(l, "t3('Hello {name}!', {name='World'})"), "Hallo World!");
}
}

@ -10,7 +10,7 @@ namespace
using namespace testing;
template <typename T>
T get(sol::state& lua, std::string luaCode)
T get(sol::state_view& lua, std::string luaCode)
{
return lua.safe_script("return " + luaCode).get<T>();
}
@ -19,7 +19,7 @@ namespace
{
// Note: LuaUtil::Callback can be used only if Lua is initialized via LuaUtil::LuaState
LuaUtil::LuaState luaState{ nullptr, nullptr };
sol::state& mLua = luaState.sol();
sol::state_view& mLua = luaState.sol();
LuaUtil::LuaStorage::initLuaBindings(mLua);
LuaUtil::LuaStorage storage(mLua);

@ -46,8 +46,9 @@ namespace sol
namespace LuaUtil
{
sol::function initL10nLoader(sol::state& lua, l10n::Manager* manager)
sol::function initL10nLoader(lua_State* L, l10n::Manager* manager)
{
sol::state_view lua(L);
sol::usertype<L10nContext> ctxDef = lua.new_usertype<L10nContext>("L10nContext");
ctxDef[sol::meta_function::call]
= [](const L10nContext& ctx, std::string_view key, sol::optional<sol::table> args) {

@ -10,7 +10,7 @@ namespace l10n
namespace LuaUtil
{
sol::function initL10nLoader(sol::state& lua, l10n::Manager* manager);
sol::function initL10nLoader(lua_State*, l10n::Manager* manager);
}
#endif // COMPONENTS_LUA_L10N_H

@ -52,21 +52,24 @@ namespace LuaUtil
"tonumber", "tostring", "type", "unpack", "xpcall", "rawequal", "rawget", "rawset", "setmetatable" };
static const std::string safePackages[] = { "coroutine", "math", "string", "table" };
static constexpr int64_t countHookStep = 2000;
static constexpr int64_t countHookStep = 1000;
bool LuaState::sProfilerEnabled = true;
void LuaState::countHook(lua_State* L, lua_Debug* ar)
{
LuaState* THIS;
(void)lua_getallocf(L, reinterpret_cast<void**>(&THIS));
if (!THIS->mActiveScriptId.mContainer)
LuaState* self;
(void)lua_getallocf(L, reinterpret_cast<void**>(&self));
if (self->mActiveScriptIdStack.empty())
return;
THIS->mActiveScriptId.mContainer->addCPUusage(THIS->mActiveScriptId.mIndex, countHookStep);
THIS->mCurrentCallInstructionCounter += countHookStep;
if (THIS->mSettings.mInstructionLimit > 0
&& THIS->mCurrentCallInstructionCounter > THIS->mSettings.mInstructionLimit)
const ScriptId& activeScript = self->mActiveScriptIdStack.back();
activeScript.mContainer->addInstructionCount(activeScript.mIndex, countHookStep);
self->mWatchdogInstructionCounter += countHookStep;
if (self->mSettings.mInstructionLimit > 0
&& self->mWatchdogInstructionCounter > self->mSettings.mInstructionLimit)
{
lua_pushstring(L,
"Lua CPU usage exceeded, probably an infinite loop in a script. "
"Lua instruction count exceeded, probably an infinite loop in a script. "
"To change the limit set \"[Lua] instruction limit per call\" in settings.cfg");
lua_error(L);
}
@ -74,9 +77,9 @@ namespace LuaUtil
void* LuaState::trackingAllocator(void* ud, void* ptr, size_t osize, size_t nsize)
{
LuaState* THIS = static_cast<LuaState*>(ud);
const uint64_t smallAllocSize = THIS->mSettings.mSmallAllocMaxSize;
const uint64_t memoryLimit = THIS->mSettings.mMemoryLimit;
LuaState* self = static_cast<LuaState*>(ud);
const uint64_t smallAllocSize = self->mSettings.mSmallAllocMaxSize;
const uint64_t memoryLimit = self->mSettings.mMemoryLimit;
if (!ptr)
osize = 0;
@ -90,14 +93,14 @@ namespace LuaUtil
else
bigAllocDelta += nsize;
if (bigAllocDelta > 0 && memoryLimit > 0 && THIS->mTotalMemoryUsage + nsize - osize > memoryLimit)
if (bigAllocDelta > 0 && memoryLimit > 0 && self->mTotalMemoryUsage + nsize - osize > memoryLimit)
{
Log(Debug::Error) << "Lua realloc " << osize << "->" << nsize
<< " is blocked because Lua memory limit (configurable in settings.cfg) is exceeded";
return nullptr;
}
THIS->mTotalMemoryUsage += smallAllocDelta + bigAllocDelta;
THIS->mSmallAllocMemoryUsage += smallAllocDelta;
self->mTotalMemoryUsage += smallAllocDelta + bigAllocDelta;
self->mSmallAllocMemoryUsage += smallAllocDelta;
void* newPtr = nullptr;
if (nsize == 0)
@ -107,59 +110,83 @@ namespace LuaUtil
if (bigAllocDelta != 0)
{
auto it = osize > smallAllocSize ? THIS->mBigAllocOwners.find(ptr) : THIS->mBigAllocOwners.end();
auto it = osize > smallAllocSize ? self->mBigAllocOwners.find(ptr) : self->mBigAllocOwners.end();
ScriptId id;
if (it != THIS->mBigAllocOwners.end())
if (it != self->mBigAllocOwners.end())
{
if (it->second.mContainer)
id = ScriptId{ *it->second.mContainer, it->second.mScriptIndex };
if (ptr != newPtr || nsize <= smallAllocSize)
THIS->mBigAllocOwners.erase(it);
self->mBigAllocOwners.erase(it);
}
else if (bigAllocDelta > 0)
{
id = THIS->mActiveScriptId;
if (!self->mActiveScriptIdStack.empty())
id = self->mActiveScriptIdStack.back();
bigAllocDelta = nsize;
}
if (id.mContainer)
{
if (static_cast<size_t>(id.mIndex) >= THIS->mMemoryUsage.size())
THIS->mMemoryUsage.resize(id.mIndex + 1);
THIS->mMemoryUsage[id.mIndex] += bigAllocDelta;
if (static_cast<size_t>(id.mIndex) >= self->mMemoryUsage.size())
self->mMemoryUsage.resize(id.mIndex + 1);
self->mMemoryUsage[id.mIndex] += bigAllocDelta;
id.mContainer->addMemoryUsage(id.mIndex, bigAllocDelta);
if (newPtr && nsize > smallAllocSize)
THIS->mBigAllocOwners.emplace(newPtr, AllocOwner{ id.mContainer->mThis, id.mIndex });
self->mBigAllocOwners.emplace(newPtr, AllocOwner{ id.mContainer->mThis, id.mIndex });
}
}
return newPtr;
}
lua_State* LuaState::createLuaRuntime(LuaState* luaState)
{
if (sProfilerEnabled)
{
Log(Debug::Info) << "Initializing LuaUtil::LuaState with profiler";
lua_State* L = lua_newstate(&trackingAllocator, luaState);
if (L)
return L;
else
{
sProfilerEnabled = false;
Log(Debug::Error)
<< "Failed to initialize LuaUtil::LuaState with custom allocator; disabling Lua profiler";
}
}
Log(Debug::Info) << "Initializing LuaUtil::LuaState without profiler";
lua_State* L = luaL_newstate();
if (!L)
throw std::runtime_error("Can't create Lua runtime");
return L;
}
LuaState::LuaState(const VFS::Manager* vfs, const ScriptsConfiguration* conf, const LuaStateSettings& settings)
: mSettings(settings)
, mLua(sol::default_at_panic, &trackingAllocator, this)
, mLuaHolder(createLuaRuntime(this))
, mSol(mLuaHolder.get())
, mConf(conf)
, mVFS(vfs)
{
lua_sethook(mLua.lua_state(), &countHook, LUA_MASKCOUNT, countHookStep);
Log(Debug::Verbose) << "Initializing LuaUtil::LuaState";
if (sProfilerEnabled)
lua_sethook(mLuaHolder.get(), &countHook, LUA_MASKCOUNT, countHookStep);
mLua.open_libraries(sol::lib::base, sol::lib::coroutine, sol::lib::math, sol::lib::bit32, sol::lib::string,
mSol.open_libraries(sol::lib::base, sol::lib::coroutine, sol::lib::math, sol::lib::bit32, sol::lib::string,
sol::lib::table, sol::lib::os, sol::lib::debug);
mLua["math"]["randomseed"](static_cast<unsigned>(std::time(nullptr)));
mLua["math"]["randomseed"] = [] {};
mSol["math"]["randomseed"](static_cast<unsigned>(std::time(nullptr)));
mSol["math"]["randomseed"] = [] {};
mLua["writeToLog"] = [](std::string_view s) { Log(Debug::Level::Info) << s; };
mSol["writeToLog"] = [](std::string_view s) { Log(Debug::Level::Info) << s; };
// Some fixes for compatibility between different Lua versions
if (mLua["unpack"] == sol::nil)
mLua["unpack"] = mLua["table"]["unpack"];
else if (mLua["table"]["unpack"] == sol::nil)
mLua["table"]["unpack"] = mLua["unpack"];
if (mSol["unpack"] == sol::nil)
mSol["unpack"] = mSol["table"]["unpack"];
else if (mSol["table"]["unpack"] == sol::nil)
mSol["table"]["unpack"] = mSol["unpack"];
if (LUA_VERSION_NUM <= 501)
{
mLua.script(R"(
mSol.script(R"(
local _pairs = pairs
local _ipairs = ipairs
pairs = function(v) return (rawget(getmetatable(v) or {}, '__pairs') or _pairs)(v) end
@ -167,7 +194,7 @@ namespace LuaUtil
)");
}
mLua.script(R"(
mSol.script(R"(
local printToLog = function(...)
local strs = {}
for i = 1, select('#', ...) do
@ -212,31 +239,24 @@ namespace LuaUtil
end
)");
mSandboxEnv = sol::table(mLua, sol::create);
mSandboxEnv["_VERSION"] = mLua["_VERSION"];
mSandboxEnv = sol::table(mSol, sol::create);
mSandboxEnv["_VERSION"] = mSol["_VERSION"];
for (const std::string& s : safeFunctions)
{
if (mLua[s] == sol::nil)
if (mSol[s] == sol::nil)
throw std::logic_error("Lua function not found: " + s);
mSandboxEnv[s] = mLua[s];
mSandboxEnv[s] = mSol[s];
}
for (const std::string& s : safePackages)
{
if (mLua[s] == sol::nil)
if (mSol[s] == sol::nil)
throw std::logic_error("Lua package not found: " + s);
mCommonPackages[s] = mSandboxEnv[s] = makeReadOnly(mLua[s]);
mCommonPackages[s] = mSandboxEnv[s] = makeReadOnly(mSol[s]);
}
mSandboxEnv["getmetatable"] = mLua["getSafeMetatable"];
mSandboxEnv["getmetatable"] = mSol["getSafeMetatable"];
mCommonPackages["os"] = mSandboxEnv["os"]
= makeReadOnly(tableFromPairs<std::string_view, sol::function>({ { "date", mLua["os"]["date"] },
{ "difftime", mLua["os"]["difftime"] }, { "time", mLua["os"]["time"] } }));
}
LuaState::~LuaState()
{
// Should be cleaned before destructing mLua.
mCommonPackages.clear();
mSandboxEnv = sol::nil;
= makeReadOnly(tableFromPairs<std::string_view, sol::function>({ { "date", mSol["os"]["date"] },
{ "difftime", mSol["os"]["difftime"] }, { "time", mSol["os"]["time"] } }));
}
sol::table makeReadOnly(const sol::table& table, bool strictIndex)
@ -280,9 +300,9 @@ namespace LuaUtil
{
sol::protected_function script = loadScriptAndCache(path);
sol::environment env(mLua, sol::create, mSandboxEnv);
sol::environment env(mSol, sol::create, mSandboxEnv);
std::string envName = namePrefix + "[" + path + "]:";
env["print"] = mLua["printGen"](envName);
env["print"] = mSol["printGen"](envName);
env["_G"] = env;
env[sol::metatable_key]["__metatable"] = false;
@ -298,18 +318,18 @@ namespace LuaUtil
else
return package;
};
sol::table loaded(mLua, sol::create);
sol::table loaded(mSol, sol::create);
for (const auto& [key, value] : mCommonPackages)
loaded[key] = maybeRunLoader(value);
for (const auto& [key, value] : packages)
loaded[key] = maybeRunLoader(value);
env["require"] = [this, env, loaded, hiddenData, scriptId](std::string_view packageName) mutable {
env["require"] = [this, env, loaded, hiddenData](std::string_view packageName) mutable {
sol::object package = loaded[packageName];
if (package != sol::nil)
return package;
sol::protected_function packageLoader = loadScriptAndCache(packageNameToVfsPath(packageName, mVFS));
sol::set_environment(env, packageLoader);
package = call(scriptId, packageLoader, packageName);
package = call(packageLoader, packageName);
loaded[packageName] = package;
return package;
};
@ -320,8 +340,8 @@ namespace LuaUtil
sol::environment LuaState::newInternalLibEnvironment()
{
sol::environment env(mLua, sol::create, mSandboxEnv);
sol::table loaded(mLua, sol::create);
sol::environment env(mSol, sol::create, mSandboxEnv);
sol::table loaded(mSol, sol::create);
for (const std::string& s : safePackages)
loaded[s] = static_cast<sol::object>(mSandboxEnv[s]);
env["require"] = [this, loaded, env](const std::string& module) mutable {
@ -347,7 +367,7 @@ namespace LuaUtil
{
auto iter = mCompiledScripts.find(path);
if (iter != mCompiledScripts.end())
return mLua.load(iter->second.as_string_view(), path, sol::load_mode::binary);
return mSol.load(iter->second.as_string_view(), path, sol::load_mode::binary);
sol::function res = loadFromVFS(path);
mCompiledScripts[path] = res.dump();
return res;
@ -356,7 +376,7 @@ namespace LuaUtil
sol::function LuaState::loadFromVFS(const std::string& path)
{
std::string fileContent(std::istreambuf_iterator<char>(*mVFS->get(path)), {});
sol::load_result res = mLua.load(fileContent, path, sol::load_mode::text);
sol::load_result res = mSol.load(fileContent, path, sol::load_mode::text);
if (!res.valid())
throw std::runtime_error("Lua error: " + res.get<std::string>());
return res;
@ -365,7 +385,7 @@ namespace LuaUtil
sol::function LuaState::loadInternalLib(std::string_view libName)
{
const auto path = packageNameToPath(libName, mLibSearchPaths);
sol::load_result res = mLua.load_file(Files::pathToUnicodeString(path), sol::load_mode::text);
sol::load_result res = mSol.load_file(Files::pathToUnicodeString(path), sol::load_mode::text);
if (!res.valid())
throw std::runtime_error("Lua error: " + res.get<std::string>());
return res;

@ -54,23 +54,21 @@ namespace LuaUtil
LuaState(const LuaState&) = delete;
LuaState(LuaState&&) = delete;
~LuaState();
// Returns underlying sol::state.
sol::state& sol() { return mLua; }
sol::state_view& sol() { return mSol; }
// Can be used by a C++ function that is called from Lua to get the Lua traceback.
// Makes no sense if called not from Lua code.
// Note: It is a slow function, should be used for debug purposes only.
std::string debugTraceback() { return mLua["debug"]["traceback"]().get<std::string>(); }
std::string debugTraceback() { return mSol["debug"]["traceback"]().get<std::string>(); }
// A shortcut to create a new Lua table.
sol::table newTable() { return sol::table(mLua, sol::create); }
sol::table newTable() { return sol::table(mSol, sol::create); }
template <typename Key, typename Value>
sol::table tableFromPairs(std::initializer_list<std::pair<Key, Value>> list)
{
sol::table res(mLua, sol::create);
sol::table res(mSol, sol::create);
for (const auto& [k, v] : list)
res[k] = v;
return res;
@ -105,7 +103,7 @@ namespace LuaUtil
sol::function loadFromVFS(const std::string& path);
sol::environment newInternalLibEnvironment();
uint64_t getTotalMemoryUsage() const { return mTotalMemoryUsage; }
uint64_t getTotalMemoryUsage() const { return mSol.memory_used(); }
uint64_t getSmallAllocMemoryUsage() const { return mSmallAllocMemoryUsage; }
uint64_t getMemoryUsageByScriptIndex(unsigned id) const
{
@ -114,6 +112,10 @@ namespace LuaUtil
const LuaStateSettings& getSettings() const { return mSettings; }
// Note: Lua profiler can not be re-enabled after disabling.
static void disableProfiler() { sProfilerEnabled = false; }
static bool isProfilerEnabled() { return sProfilerEnabled; }
private:
static sol::protected_function_result throwIfError(sol::protected_function_result&&);
template <typename... Args>
@ -126,6 +128,8 @@ namespace LuaUtil
static void countHook(lua_State* L, lua_Debug* ar);
static void* trackingAllocator(void* ud, void* ptr, size_t osize, size_t nsize);
lua_State* createLuaRuntime(LuaState* luaState);
struct AllocOwner
{
std::shared_ptr<ScriptsContainer*> mContainer;
@ -134,21 +138,43 @@ namespace LuaUtil
const LuaStateSettings mSettings;
// Needed to track resource usage per script, must be initialized before mLua.
ScriptId mActiveScriptId;
uint64_t mCurrentCallInstructionCounter = 0;
// Needed to track resource usage per script, must be initialized before mLuaHolder.
std::vector<ScriptId> mActiveScriptIdStack;
uint64_t mWatchdogInstructionCounter = 0;
std::map<void*, AllocOwner> mBigAllocOwners;
uint64_t mTotalMemoryUsage = 0;
uint64_t mSmallAllocMemoryUsage = 0;
std::vector<int64_t> mMemoryUsage;
sol::state mLua;
class LuaStateHolder
{
public:
LuaStateHolder(lua_State* L)
: L(L)
{
sol::set_default_state(L);
}
~LuaStateHolder() { lua_close(L); }
LuaStateHolder(const LuaStateHolder&) = delete;
LuaStateHolder(LuaStateHolder&&) = delete;
lua_State* get() { return L; }
private:
lua_State* L;
};
// Must be declared before mSol and all sol-related objects. Then on exit it will be destructed the last.
LuaStateHolder mLuaHolder;
sol::state_view mSol;
const ScriptsConfiguration* mConf;
sol::table mSandboxEnv;
std::map<std::string, sol::bytecode> mCompiledScripts;
std::map<std::string, sol::object> mCommonPackages;
const VFS::Manager* mVFS;
std::vector<std::filesystem::path> mLibSearchPaths;
static bool sProfilerEnabled;
};
// LuaUtil::call should be used for every call of every Lua function.
@ -178,25 +204,30 @@ namespace LuaUtil
template <typename... Args>
sol::protected_function_result call(ScriptId scriptId, const sol::protected_function& fn, Args&&... args)
{
LuaState* luaState;
(void)lua_getallocf(fn.lua_state(), reinterpret_cast<void**>(&luaState));
assert(luaState->mActiveScriptId.mContainer == nullptr && "recursive call of LuaUtil::call(scriptId, ...) ?");
luaState->mActiveScriptId = scriptId;
luaState->mCurrentCallInstructionCounter = 0;
LuaState* luaState = nullptr;
if (LuaState::sProfilerEnabled && scriptId.mContainer)
{
(void)lua_getallocf(fn.lua_state(), reinterpret_cast<void**>(&luaState));
luaState->mActiveScriptIdStack.push_back(scriptId);
luaState->mWatchdogInstructionCounter = 0;
}
try
{
auto res = LuaState::throwIfError(fn(std::forward<Args>(args)...));
luaState->mActiveScriptId = {};
if (luaState)
luaState->mActiveScriptIdStack.pop_back();
return res;
}
catch (std::exception&)
{
luaState->mActiveScriptId = {};
if (luaState)
luaState->mActiveScriptIdStack.pop_back();
throw;
}
catch (...)
{
luaState->mActiveScriptId = {};
if (luaState)
luaState->mActiveScriptIdStack.pop_back();
throw std::runtime_error("Unknown error");
}
}

@ -75,7 +75,7 @@ namespace LuaUtil
script.mHiddenData[sScriptIdKey] = ScriptId{ this, scriptId };
script.mHiddenData[sScriptDebugNameKey] = debugName;
script.mPath = path;
script.mStats.mCPUusage = 0;
script.mStats.mAvgInstructionCount = 0;
const auto oldMemoryUsageIt = mRemovedScriptsMemoryUsage.find(scriptId);
if (oldMemoryUsageIt != mRemovedScriptsMemoryUsage.end())
@ -590,24 +590,24 @@ namespace LuaUtil
updateTimerQueue(mGameTimersQueue, gameTime);
}
static constexpr float CPUusageAvgCoef = 1.0 / 30; // averaging over approximately 30 frames
static constexpr float instructionCountAvgCoef = 1.0 / 30; // averaging over approximately 30 frames
void ScriptsContainer::CPUusageNextFrame()
void ScriptsContainer::statsNextFrame()
{
for (auto& [scriptId, script] : mScripts)
{
// The averaging formula is: averageValue = averageValue * (1-c) + newValue * c
script.mStats.mCPUusage *= 1 - CPUusageAvgCoef;
if (script.mStats.mCPUusage < 5)
script.mStats.mCPUusage = 0; // speeding up converge to zero if newValue is zero
script.mStats.mAvgInstructionCount *= 1 - instructionCountAvgCoef;
if (script.mStats.mAvgInstructionCount < 5)
script.mStats.mAvgInstructionCount = 0; // speeding up converge to zero if newValue is zero
}
}
void ScriptsContainer::addCPUusage(int scriptId, int64_t CPUusage)
void ScriptsContainer::addInstructionCount(int scriptId, int64_t instructionCount)
{
auto it = mScripts.find(scriptId);
if (it != mScripts.end())
it->second.mStats.mCPUusage += CPUusage * CPUusageAvgCoef;
it->second.mStats.mAvgInstructionCount += instructionCount * instructionCountAvgCoef;
}
void ScriptsContainer::addMemoryUsage(int scriptId, int64_t memoryDelta)
@ -640,7 +640,7 @@ namespace LuaUtil
stats.resize(mLua.getConfiguration().size());
for (auto& [id, script] : mScripts)
{
stats[id].mCPUusage += script.mStats.mCPUusage;
stats[id].mAvgInstructionCount += script.mStats.mAvgInstructionCount;
stats[id].mMemoryUsage += script.mStats.mMemoryUsage;
}
for (auto& [id, mem] : mRemovedScriptsMemoryUsage)

@ -146,12 +146,12 @@ namespace LuaUtil
// because they can not be stored in saves. I.e. loading a saved game will not fully restore the state.
void setupUnsavableTimer(TimerType type, double time, int scriptId, sol::main_protected_function callback);
// Informs that new frame is started. Needed to track CPU usage per frame.
void CPUusageNextFrame();
// Informs that new frame is started. Needed to track Lua instruction count per frame.
void statsNextFrame();
struct ScriptStats
{
float mCPUusage = 0; // averaged number of Lua instructions per frame
float mAvgInstructionCount = 0; // averaged number of Lua instructions per frame
int64_t mMemoryUsage = 0; // bytes
};
void collectStats(std::vector<ScriptStats>& stats) const;
@ -227,7 +227,7 @@ namespace LuaUtil
using EventHandlerList = std::vector<Handler>;
friend class LuaState;
void addCPUusage(int scriptId, int64_t CPUusage);
void addInstructionCount(int scriptId, int64_t instructionCount);
void addMemoryUsage(int scriptId, int64_t memoryDelta);
// Add to container without calling onInit/onLoad.

@ -89,8 +89,9 @@ namespace LuaUtil
}
}
sol::table initUtilPackage(sol::state& lua)
sol::table initUtilPackage(lua_State* L)
{
sol::state_view lua(L);
sol::table util(lua, sol::create);
// Lua bindings for Vec2

@ -34,8 +34,7 @@ namespace LuaUtil
return { q };
}
sol::table initUtilPackage(sol::state&);
sol::table initUtilPackage(lua_State*);
}
#endif // COMPONENTS_LUA_UTILPACKAGE_H

@ -1141,17 +1141,21 @@ lua debug = false
# If zero, Lua scripts are processed in the main thread.
lua num threads = 1
# Enable Lua profiler
lua profiler = true
# No ownership tracking for allocations below or equal this size.
small alloc max size = 1024
# Memory limit for Lua runtime. If exceeded then only small allocations are allowed. Small allocations are always allowed, so e.g. Lua console can function.
# Default value is 2GB.
# Memory limit for Lua runtime (only if lua profiler = true). If exceeded then only small allocations are allowed.
# Small allocations are always allowed, so e.g. Lua console can function. Default value is 2GB.
memory limit = 2147483648
# Print debug info about memory usage.
# Print debug info about memory usage (only if lua profiler = true).
log memory usage = false
# The maximal number of Lua instructions per function call. If exceeded (e.g. because of an infinite loop) the function will be terminated.
# The maximal number of Lua instructions per function call (only if lua profiler = true).
# If exceeded (e.g. because of an infinite loop) the function will be terminated.
instruction limit per call = 100000000
[Stereo]

Loading…
Cancel
Save