V8 memory usage statistics

This commit is contained in:
ksuprynowicz 2023-04-23 22:44:12 +02:00
parent 2faacfaec6
commit 56c3bad002
6 changed files with 124 additions and 27 deletions

View file

@ -46,6 +46,14 @@ inline ScriptValue scriptValueFromValue(ScriptEngine* engine, const T& t);
template <typename T>
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);

View file

@ -12,42 +12,54 @@
#include "ScriptManager.h"
#include "ScriptManagerScriptingInterface.h"
#include "ScriptEngine.h"
#include <QMetaType>
ScriptManagerScriptingInterface::ScriptManagerScriptingInterface(ScriptManager *parent): QObject(parent), _manager(parent) {
qRegisterMetaType<ScriptException>();
qRegisterMetaType<ScriptEngineException>();
qRegisterMetaType<ScriptRuntimeException>();
qRegisterMetaType<ScriptException>();
qRegisterMetaType<ScriptEngineException>();
qRegisterMetaType<ScriptRuntimeException>();
qRegisterMetaType<std::shared_ptr<ScriptException>>();
qRegisterMetaType<std::shared_ptr<ScriptEngineException>>();
qRegisterMetaType<std::shared_ptr<ScriptRuntimeException>>();
qRegisterMetaType<std::shared_ptr<ScriptException>>();
qRegisterMetaType<std::shared_ptr<ScriptEngineException>>();
qRegisterMetaType<std::shared_ptr<ScriptRuntimeException>>();
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<ScriptException> exception) {
// V8TODO: What should we actually handle here?
void ScriptManagerScriptingInterface::scriptManagerException(std::shared_ptr<ScriptException> exception) {
// V8TODO: What should we actually handle here?
// emit unhandledException(exception.thrownValue);
}
// 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;
}

View file

@ -479,6 +479,30 @@ public:
*/
Q_INVOKABLE QString getExternalPath(ExternalResource::Bucket bucket, const QString& path) { return _manager->getExternalPath(bucket, path); }
/**jsdoc
* <p>Object containing memory usage statistics data.</p>
* <table>
* <thead>
* <tr><th>Name</th><th>Type</th><th>Description</th></tr>
* </thead>
* <tbody>
* <tr><td><code>totalHeapSize</code></td><td>{number}</td><td>Total heap size allocated by scripting engine.</td></tr>
* <tr><td><code>usedHeapSize</code></td><td>{number}</td><td></td>Amount of heap memory that is currently in use.</tr>
* <tr><td><code>totalAvailableSize</code></td><td>{number}</td><td>Amount of remaining available heap memory</td></tr>
* <tr><td><code>totalGlobalHandlesSize</code></td><td>{number}</td><td>V8-specific property</td></tr>
* <tr><td><code>usedGlobalHandlesSize</code></td><td>{number}</td><td>V8-specific property</td></tr>
* </tbody>
* </table>
* @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

View file

@ -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;
}

View file

@ -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); }

View file

@ -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);
});