From a57c7a5e5be0445cd8923aa87fc7f02cb76dbdc6 Mon Sep 17 00:00:00 2001 From: ksuprynowicz Date: Sat, 29 Apr 2023 19:34:49 +0200 Subject: [PATCH] V8 heap object statistics --- libraries/script-engine/src/ScriptEngine.h | 15 ++++++-- .../src/ScriptManagerScriptingInterface.cpp | 10 +++++- .../src/ScriptManagerScriptingInterface.h | 14 +++++++- .../script-engine/src/v8/ScriptEngineV8.cpp | 35 ++++++++++++++++--- .../script-engine/src/v8/ScriptEngineV8.h | 6 ++-- libraries/script-engine/src/v8/V8Types.h | 8 ++--- 6 files changed, 74 insertions(+), 14 deletions(-) diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index e7d638afa0..a856f0c1a8 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -27,7 +27,8 @@ #include "ScriptException.h" // These are used for debugging memory leaks caused by persistent handles -#define OVERTE_V8_HANDLE_COUNTERS +// V8TODO: Rename to something better, like for example OVERTE_V8_MEMORY_DEBUG +#define OVERTE_V8_MEMORY_DEBUG class QByteArray; class QLatin1String; @@ -56,7 +57,7 @@ public: size_t totalAvailableSize; size_t totalGlobalHandlesSize; size_t usedGlobalHandlesSize; -#ifdef OVERTE_V8_HANDLE_COUNTERS +#ifdef OVERTE_V8_MEMORY_DEBUG size_t scriptValueCount; size_t scriptValueProxyCount; #endif @@ -399,6 +400,16 @@ public: */ virtual ScriptEngineMemoryStatistics getMemoryUsageStatistics() = 0; + /** + * @brief Start collecting object statistics that can later be reported with dumpHeapObjectStatistics(). + */ + virtual void startCollectingObjectStatistics() = 0; + + /** + * @brief Prints heap statistics to a file. Collecting needs to first be started with dumpHeapObjectStatistics(). + */ + virtual void dumpHeapObjectStatistics() = 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 a5b9ebb13b..5ce8a5666b 100644 --- a/libraries/script-engine/src/ScriptManagerScriptingInterface.cpp +++ b/libraries/script-engine/src/ScriptManagerScriptingInterface.cpp @@ -61,13 +61,21 @@ QVariantMap ScriptManagerScriptingInterface::getMemoryUsageStatistics() { map.insert("totalAvailableSize", QVariant((qulonglong)(statistics.totalAvailableSize))); map.insert("totalGlobalHandlesSize", QVariant((qulonglong)(statistics.totalGlobalHandlesSize))); map.insert("usedGlobalHandlesSize", QVariant((qulonglong)(statistics.usedGlobalHandlesSize))); -#ifdef OVERTE_V8_HANDLE_COUNTERS +#ifdef OVERTE_V8_MEMORY_DEBUG map.insert("scriptValueCount", QVariant((qulonglong)(statistics.scriptValueCount))); map.insert("scriptValueProxyCount", QVariant((qulonglong)(statistics.scriptValueProxyCount))); #endif return map; } +void ScriptManagerScriptingInterface::startCollectingObjectStatistics() { + _manager->engine()->startCollectingObjectStatistics(); +} + +void ScriptManagerScriptingInterface::dumpHeapObjectStatistics() { + _manager->engine()->dumpHeapObjectStatistics(); +} + ScriptValue ScriptManagerScriptingInterface::createGarbageCollectorDebuggingObject() { //auto value = _manager->engine()->newQObject(new TestQObject, ScriptEngine::ScriptOwnership); return _manager->engine()->newQObject(new TestQObject, ScriptEngine::ScriptOwnership); diff --git a/libraries/script-engine/src/ScriptManagerScriptingInterface.h b/libraries/script-engine/src/ScriptManagerScriptingInterface.h index 8d37b2e7a3..2b9aeb49e8 100644 --- a/libraries/script-engine/src/ScriptManagerScriptingInterface.h +++ b/libraries/script-engine/src/ScriptManagerScriptingInterface.h @@ -53,7 +53,7 @@ class TestQObject : public QObject { Q_OBJECT public: - Q_INVOKABLE virtual void testMethod() { qDebug() << "TestQObject::testMethod"; }; + //Q_INVOKABLE virtual void testMethod() { qDebug() << "TestQObject::testMethod"; }; }; class ScriptManagerScriptingInterface : public QObject { @@ -510,6 +510,18 @@ public: */ Q_INVOKABLE QVariantMap getMemoryUsageStatistics(); + /**jsdoc + * Start collecting object statistics that can later be reported with Script.dumpHeapObjectStatistics(). + * @function Script.dumpHeapObjectStatistics + */ + Q_INVOKABLE void startCollectingObjectStatistics(); + + /**jsdoc + * Prints heap statistics to a file. Collecting needs to first be started with Script.dumpHeapObjectStatistics(). + * @function Script.dumpHeapObjectStatistics + */ + Q_INVOKABLE void dumpHeapObjectStatistics(); + /**jsdoc * Create test object for garbage collector debugging. * @function Script.createGarbageCollectorDebuggingObject() diff --git a/libraries/script-engine/src/v8/ScriptEngineV8.cpp b/libraries/script-engine/src/v8/ScriptEngineV8.cpp index 07d8423a2a..ff8d96a707 100644 --- a/libraries/script-engine/src/v8/ScriptEngineV8.cpp +++ b/libraries/script-engine/src/v8/ScriptEngineV8.cpp @@ -42,6 +42,8 @@ #include +#include + #include "../ScriptEngineLogging.h" #include "../ScriptProgram.h" #include "../ScriptValue.h" @@ -282,7 +284,11 @@ ScriptEngineV8::ScriptEngineV8(ScriptManager *manager) : ScriptEngine(manager), // --assert-types v8::V8::InitializeICU(); - v8::V8::SetFlagsFromString("--stack-size=256 --verify-heap --assert-types"); +#ifdef OVERTE_V8_MEMORY_DEBUG + v8::V8::SetFlagsFromString("--stack-size=256 --track_gc_object_stats --assert-types"); +#else + v8::V8::SetFlagsFromString("--stack-size=256"); +#endif //v8::V8::SetFlagsFromString("--stack-size=256 --single-threaded"); v8::Platform* platform = getV8Platform(); v8::V8::InitializePlatform(platform); @@ -1720,10 +1726,31 @@ ScriptEngineMemoryStatistics ScriptEngineV8::getMemoryUsageStatistics() { statistics.totalAvailableSize = heapStatistics.total_available_size(); statistics.totalGlobalHandlesSize = heapStatistics.total_global_handles_size(); statistics.usedGlobalHandlesSize = heapStatistics.used_global_handles_size(); -#ifdef OVERTE_V8_HANDLE_COUNTERS +#ifdef OVERTE_V8_MEMORY_DEBUG statistics.scriptValueCount = scriptValueCount; statistics.scriptValueProxyCount = scriptValueProxyCount; #endif - return statistics; -} \ No newline at end of file +} + +void ScriptEngineV8::startCollectingObjectStatistics() { + auto heapProfiler = _v8Isolate->GetHeapProfiler(); + heapProfiler->StartTrackingHeapObjects(); +} + +void ScriptEngineV8::dumpHeapObjectStatistics() { + // V8TODO: this is not very elegant, but very convenient + QFile dumpFile("/tmp/heap_objectStatistics_dump.csv"); + if (!dumpFile.open(QFile::WriteOnly | QFile::Truncate)) { + return; + } + QTextStream dump(&dumpFile); + size_t objectTypeCount = _v8Isolate->NumberOfTrackedHeapObjectTypes(); + for (size_t i = 0; i < objectTypeCount; i++) { + v8::HeapObjectStatistics statistics; + if (_v8Isolate->GetHeapObjectStatisticsAtLastGC(&statistics, i)) { + dump << statistics.object_type() << " " << statistics.object_sub_type() << " " << statistics.object_count() << " " + << statistics.object_size() << "\n"; + } + } +} diff --git a/libraries/script-engine/src/v8/ScriptEngineV8.h b/libraries/script-engine/src/v8/ScriptEngineV8.h index c8bb460ef1..2ab8491d03 100644 --- a/libraries/script-engine/src/v8/ScriptEngineV8.h +++ b/libraries/script-engine/src/v8/ScriptEngineV8.h @@ -136,6 +136,8 @@ public: // ScriptEngine implementation QString scriptValueDebugListMembersV8(const V8ScriptValue &v8Value); virtual void logBacktrace(const QString &title = QString("")) override; virtual ScriptEngineMemoryStatistics getMemoryUsageStatistics() override; + virtual void startCollectingObjectStatistics() override; + virtual void dumpHeapObjectStatistics() 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); } @@ -205,7 +207,7 @@ public: // not for public use, but I don't like how Qt strings this along with p void popContext(); // V8TODO: call this after initializing global object void storeGlobalObjectContents(); -#ifdef OVERTE_V8_HANDLE_COUNTERS +#ifdef OVERTE_V8_MEMORY_DEBUG void incrementScriptValueCounter() { scriptValueCount++; }; void decrementScriptValueCounter() { scriptValueCount--; }; void incrementScriptValueProxyCounter() { scriptValueProxyCount++; }; @@ -264,7 +266,7 @@ protected: //ArrayBufferClass* _arrayBufferClass; // Counts how many nested evaluate calls are there at a given point int _evaluatingCounter; -#ifdef OVERTE_V8_HANDLE_COUNTERS +#ifdef OVERTE_V8_MEMORY_DEBUG std::atomic scriptValueCount{0}; std::atomic scriptValueProxyCount{0}; #endif diff --git a/libraries/script-engine/src/v8/V8Types.h b/libraries/script-engine/src/v8/V8Types.h index fc67e45691..2c52175c9d 100644 --- a/libraries/script-engine/src/v8/V8Types.h +++ b/libraries/script-engine/src/v8/V8Types.h @@ -47,7 +47,7 @@ public: return; } Q_ASSERT(false);*/ -#ifdef OVERTE_V8_HANDLE_COUNTERS +#ifdef OVERTE_V8_MEMORY_DEBUG _engine->incrementScriptValueCounter(); #endif _value.reset(new v8::UniquePersistent(_engine->getIsolate(), value)); @@ -70,7 +70,7 @@ public: v8::HandleScope handleScope(_engine->getIsolate()); v8::Context::Scope(_engine->getContext()); //_value.reset(new v8::UniquePersistent(_engine->getIsolate(), v8::Local())); -#ifdef OVERTE_V8_HANDLE_COUNTERS +#ifdef OVERTE_V8_MEMORY_DEBUG _engine->incrementScriptValueCounter(); #endif _value.reset(new v8::UniquePersistent(_engine->getIsolate(), v8::Local())); @@ -82,7 +82,7 @@ public: v8::HandleScope handleScope(_engine->getIsolate()); v8::Context::Scope(_engine->getContext()); //_value.reset(new v8::UniquePersistent(_engine->getIsolate(), copied.constGet())); -#ifdef OVERTE_V8_HANDLE_COUNTERS +#ifdef OVERTE_V8_MEMORY_DEBUG _engine->incrementScriptValueCounter(); #endif _value.reset(new v8::UniquePersistent(_engine->getIsolate(), copied.constGet())); @@ -136,7 +136,7 @@ public: v8::Isolate::Scope isolateScope(_engine->getIsolate()); v8::HandleScope handleScope(_engine->getIsolate()); //v8::Context::Scope(_engine->getContext()); -#ifdef OVERTE_V8_HANDLE_COUNTERS +#ifdef OVERTE_V8_MEMORY_DEBUG _engine->decrementScriptValueCounter(); #endif _value->Reset();