From 56c3bad002ad23416ed1e151e785e35208d0ab01 Mon Sep 17 00:00:00 2001 From: ksuprynowicz Date: Sun, 23 Apr 2023 22:44:12 +0200 Subject: [PATCH] V8 memory usage statistics --- libraries/script-engine/src/ScriptEngine.h | 18 +++++ .../src/ScriptManagerScriptingInterface.cpp | 66 +++++++++++-------- .../src/ScriptManagerScriptingInterface.h | 24 +++++++ .../script-engine/src/v8/ScriptEngineV8.cpp | 15 +++++ .../script-engine/src/v8/ScriptEngineV8.h | 1 + .../developer/debugging/scriptMemoryReport.js | 27 ++++++++ 6 files changed, 124 insertions(+), 27 deletions(-) create mode 100644 scripts/developer/debugging/scriptMemoryReport.js diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index a69376a461..bb4a20af93 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -46,6 +46,14 @@ inline ScriptValue scriptValueFromValue(ScriptEngine* engine, const T& t); template inline T scriptvalue_cast(const ScriptValue& value); +class ScriptEngineMemoryStatistics { +public: + size_t totalHeapSize; + size_t usedHeapSize; + size_t totalAvailableSize; + size_t totalGlobalHandlesSize; + size_t usedGlobalHandlesSize; +}; /** * @brief Provides an engine-independent interface for a scripting engine @@ -374,6 +382,16 @@ public: * @param title Informative title for the backtrace */ virtual void logBacktrace(const QString &title) = 0; + + /** + * @brief Return memory usage statistics data. + * + * Returns memory usage statistics data for debugging. + * + * @return ScriptEngineMemoryStatistics Object containing memory usage statistics data. + */ + virtual ScriptEngineMemoryStatistics getMemoryUsageStatistics() = 0; + public: // helper to detect and log warnings when other code invokes QScriptEngine/BaseScriptEngine in thread-unsafe ways bool IS_THREADSAFE_INVOCATION(const QString& method); diff --git a/libraries/script-engine/src/ScriptManagerScriptingInterface.cpp b/libraries/script-engine/src/ScriptManagerScriptingInterface.cpp index 8c7a2d75b0..04dc992b5e 100644 --- a/libraries/script-engine/src/ScriptManagerScriptingInterface.cpp +++ b/libraries/script-engine/src/ScriptManagerScriptingInterface.cpp @@ -12,42 +12,54 @@ #include "ScriptManager.h" #include "ScriptManagerScriptingInterface.h" +#include "ScriptEngine.h" #include ScriptManagerScriptingInterface::ScriptManagerScriptingInterface(ScriptManager *parent): QObject(parent), _manager(parent) { - qRegisterMetaType(); - qRegisterMetaType(); - qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); + qRegisterMetaType(); - qRegisterMetaType>(); - qRegisterMetaType>(); - qRegisterMetaType>(); + qRegisterMetaType>(); + qRegisterMetaType>(); + qRegisterMetaType>(); - connect(_manager, &ScriptManager::scriptLoaded, this, &ScriptManagerScriptingInterface::scriptLoaded); - connect(_manager, &ScriptManager::errorLoadingScript, this, &ScriptManagerScriptingInterface::errorLoadingScript); - connect(_manager, &ScriptManager::update, this, &ScriptManagerScriptingInterface::update); - connect(_manager, &ScriptManager::scriptEnding, this, &ScriptManagerScriptingInterface::scriptEnding); - connect(_manager, &ScriptManager::finished, this, &ScriptManagerScriptingInterface::finished); - connect(_manager, &ScriptManager::printedMessage, this, &ScriptManagerScriptingInterface::printedMessage); - connect(_manager, &ScriptManager::errorMessage, this, &ScriptManagerScriptingInterface::errorMessage); - connect(_manager, &ScriptManager::warningMessage, this, &ScriptManagerScriptingInterface::warningMessage); - connect(_manager, &ScriptManager::infoMessage, this, &ScriptManagerScriptingInterface::infoMessage); - connect(_manager, &ScriptManager::runningStateChanged, this, &ScriptManagerScriptingInterface::runningStateChanged); - connect(_manager, &ScriptManager::clearDebugWindow, this, &ScriptManagerScriptingInterface::clearDebugWindow); - connect(_manager, &ScriptManager::loadScript, this, &ScriptManagerScriptingInterface::loadScript); - connect(_manager, &ScriptManager::doneRunning, this, &ScriptManagerScriptingInterface::doneRunning); - connect(_manager, &ScriptManager::entityScriptDetailsUpdated, this, &ScriptManagerScriptingInterface::entityScriptDetailsUpdated); - connect(_manager, &ScriptManager::entityScriptPreloadFinished, this, &ScriptManagerScriptingInterface::entityScriptPreloadFinished); - connect(_manager, &ScriptManager::unhandledException, this, &ScriptManagerScriptingInterface::scriptManagerException); - } + connect(_manager, &ScriptManager::scriptLoaded, this, &ScriptManagerScriptingInterface::scriptLoaded); + connect(_manager, &ScriptManager::errorLoadingScript, this, &ScriptManagerScriptingInterface::errorLoadingScript); + connect(_manager, &ScriptManager::update, this, &ScriptManagerScriptingInterface::update); + connect(_manager, &ScriptManager::scriptEnding, this, &ScriptManagerScriptingInterface::scriptEnding); + connect(_manager, &ScriptManager::finished, this, &ScriptManagerScriptingInterface::finished); + connect(_manager, &ScriptManager::printedMessage, this, &ScriptManagerScriptingInterface::printedMessage); + connect(_manager, &ScriptManager::errorMessage, this, &ScriptManagerScriptingInterface::errorMessage); + connect(_manager, &ScriptManager::warningMessage, this, &ScriptManagerScriptingInterface::warningMessage); + connect(_manager, &ScriptManager::infoMessage, this, &ScriptManagerScriptingInterface::infoMessage); + connect(_manager, &ScriptManager::runningStateChanged, this, &ScriptManagerScriptingInterface::runningStateChanged); + connect(_manager, &ScriptManager::clearDebugWindow, this, &ScriptManagerScriptingInterface::clearDebugWindow); + connect(_manager, &ScriptManager::loadScript, this, &ScriptManagerScriptingInterface::loadScript); + connect(_manager, &ScriptManager::doneRunning, this, &ScriptManagerScriptingInterface::doneRunning); + connect(_manager, &ScriptManager::entityScriptDetailsUpdated, this, &ScriptManagerScriptingInterface::entityScriptDetailsUpdated); + connect(_manager, &ScriptManager::entityScriptPreloadFinished, this, &ScriptManagerScriptingInterface::entityScriptPreloadFinished); + connect(_manager, &ScriptManager::unhandledException, this, &ScriptManagerScriptingInterface::scriptManagerException); +} - void ScriptManagerScriptingInterface::scriptManagerException(std::shared_ptr exception) { - // V8TODO: What should we actually handle here? +void ScriptManagerScriptingInterface::scriptManagerException(std::shared_ptr exception) { + // V8TODO: What should we actually handle here? - // emit unhandledException(exception.thrownValue); - } \ No newline at end of file + // emit unhandledException(exception.thrownValue); +} + +QVariantMap ScriptManagerScriptingInterface::getMemoryUsageStatistics() { + auto statistics = _manager->engine()->getMemoryUsageStatistics(); + QVariantMap map; + map.insert("totalHeapSize", QVariant((qulonglong)(statistics.totalHeapSize))); + map.insert("usedHeapSize", QVariant((qulonglong)(statistics.usedHeapSize))); + map.insert("totalAvailableSize", QVariant((qulonglong)(statistics.totalAvailableSize))); + map.insert("totalGlobalHandlesSize", QVariant((qulonglong)(statistics.totalGlobalHandlesSize))); + map.insert("usedGlobalHandlesSize", QVariant((qulonglong)(statistics.usedGlobalHandlesSize))); + return map; +} diff --git a/libraries/script-engine/src/ScriptManagerScriptingInterface.h b/libraries/script-engine/src/ScriptManagerScriptingInterface.h index 20794afc02..507feaec0f 100644 --- a/libraries/script-engine/src/ScriptManagerScriptingInterface.h +++ b/libraries/script-engine/src/ScriptManagerScriptingInterface.h @@ -479,6 +479,30 @@ public: */ Q_INVOKABLE QString getExternalPath(ExternalResource::Bucket bucket, const QString& path) { return _manager->getExternalPath(bucket, path); } + /**jsdoc + *

Object containing memory usage statistics data.

+ * + * + * + * + * + * + * Amount of heap memory that is currently in use. + * + * + * + * + *
NameTypeDescription
totalHeapSize{number}Total heap size allocated by scripting engine.
usedHeapSize{number}
totalAvailableSize{number}Amount of remaining available heap memory
totalGlobalHandlesSize{number}V8-specific property
usedGlobalHandlesSize{number}V8-specific property
+ * @typedef {object} Script.MemoryUsageData + */ + + /**jsdoc + * Returns memory usage statistics data. + * @function Script.getMemoryUsageStatistics + * @Returns {Script.MemoryUsageData} Object containing statistics about memory usage. + */ + Q_INVOKABLE QVariantMap getMemoryUsageStatistics(); + signals: /**jsdoc diff --git a/libraries/script-engine/src/v8/ScriptEngineV8.cpp b/libraries/script-engine/src/v8/ScriptEngineV8.cpp index 746483cc8a..31a5e3e1ab 100644 --- a/libraries/script-engine/src/v8/ScriptEngineV8.cpp +++ b/libraries/script-engine/src/v8/ScriptEngineV8.cpp @@ -1708,3 +1708,18 @@ QStringList ScriptEngineV8::getCurrentScriptURLs() const { } return scriptURLs; } + +ScriptEngineMemoryStatistics ScriptEngineV8::getMemoryUsageStatistics() { + v8::Locker locker(_v8Isolate); + v8::Isolate::Scope isolateScope(_v8Isolate); + ScriptEngineMemoryStatistics statistics; + v8::HeapStatistics heapStatistics; + _v8Isolate->GetHeapStatistics(&heapStatistics); + statistics.totalHeapSize = heapStatistics.total_available_size(); + statistics.usedHeapSize = heapStatistics.used_heap_size(); + statistics.totalAvailableSize = heapStatistics.total_available_size(); + statistics.totalGlobalHandlesSize = heapStatistics.total_global_handles_size(); + statistics.usedGlobalHandlesSize = heapStatistics.used_global_handles_size(); + + return statistics; +} \ No newline at end of file diff --git a/libraries/script-engine/src/v8/ScriptEngineV8.h b/libraries/script-engine/src/v8/ScriptEngineV8.h index 0a3ef444fb..99cd5a70db 100644 --- a/libraries/script-engine/src/v8/ScriptEngineV8.h +++ b/libraries/script-engine/src/v8/ScriptEngineV8.h @@ -135,6 +135,7 @@ public: // ScriptEngine implementation virtual QString scriptValueDebugListMembers(const ScriptValue &value) override; QString scriptValueDebugListMembersV8(const V8ScriptValue &v8Value); virtual void logBacktrace(const QString &title = QString("")) override; + virtual ScriptEngineMemoryStatistics getMemoryUsageStatistics() override; // helper to detect and log warnings when other code invokes QScriptEngine/BaseScriptEngine in thread-unsafe ways inline bool IS_THREADSAFE_INVOCATION(const QString& method) { return ScriptEngine::IS_THREADSAFE_INVOCATION(method); } diff --git a/scripts/developer/debugging/scriptMemoryReport.js b/scripts/developer/debugging/scriptMemoryReport.js new file mode 100644 index 0000000000..d5c8402f32 --- /dev/null +++ b/scripts/developer/debugging/scriptMemoryReport.js @@ -0,0 +1,27 @@ +// +// scriptMemoryReport.js +// scripts/developer/debugging +// +// Created by dr Karol Suprynowicz on 2023/04/23 +// Copyright 2023 Overte e.V. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// SPDX-License-Identifier: Apache-2.0 +// + +// A simple script that prints memory usage statistics for a given script engine every 5 seconds +// It can be included for example as a part of default scripts or controller scripts + +var memoryStatisticsIntervalHandle = Script.setInterval(function () { + statistics = Script.getMemoryUsageStatistics(); + print("Script memory usage: Total heap size: " + statistics.totalHeapSize + + " usedHeapSize: " + statistics.usedHeapSize + + " totalAvailableSize: " + statistics.totalAvailableSize + + " totalGlobalHandlesSize: " + statistics.totalGlobalHandlesSize + + " usedGlobalHandlesSize: " + statistics.usedGlobalHandlesSize); +}, 5000); + +Script.scriptEnding.connect(function () { + Script.clearInterval(memoryStatisticsIntervalHandle); +});