V8 heap object statistics

This commit is contained in:
ksuprynowicz 2023-04-29 19:34:49 +02:00
parent 6466d39c05
commit a57c7a5e5b
6 changed files with 74 additions and 14 deletions

View file

@ -27,7 +27,8 @@
#include "ScriptException.h" #include "ScriptException.h"
// These are used for debugging memory leaks caused by persistent handles // 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 QByteArray;
class QLatin1String; class QLatin1String;
@ -56,7 +57,7 @@ public:
size_t totalAvailableSize; size_t totalAvailableSize;
size_t totalGlobalHandlesSize; size_t totalGlobalHandlesSize;
size_t usedGlobalHandlesSize; size_t usedGlobalHandlesSize;
#ifdef OVERTE_V8_HANDLE_COUNTERS #ifdef OVERTE_V8_MEMORY_DEBUG
size_t scriptValueCount; size_t scriptValueCount;
size_t scriptValueProxyCount; size_t scriptValueProxyCount;
#endif #endif
@ -399,6 +400,16 @@ public:
*/ */
virtual ScriptEngineMemoryStatistics getMemoryUsageStatistics() = 0; 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: public:
// helper to detect and log warnings when other code invokes QScriptEngine/BaseScriptEngine in thread-unsafe ways // helper to detect and log warnings when other code invokes QScriptEngine/BaseScriptEngine in thread-unsafe ways
bool IS_THREADSAFE_INVOCATION(const QString& method); bool IS_THREADSAFE_INVOCATION(const QString& method);

View file

@ -61,13 +61,21 @@ QVariantMap ScriptManagerScriptingInterface::getMemoryUsageStatistics() {
map.insert("totalAvailableSize", QVariant((qulonglong)(statistics.totalAvailableSize))); map.insert("totalAvailableSize", QVariant((qulonglong)(statistics.totalAvailableSize)));
map.insert("totalGlobalHandlesSize", QVariant((qulonglong)(statistics.totalGlobalHandlesSize))); map.insert("totalGlobalHandlesSize", QVariant((qulonglong)(statistics.totalGlobalHandlesSize)));
map.insert("usedGlobalHandlesSize", QVariant((qulonglong)(statistics.usedGlobalHandlesSize))); 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("scriptValueCount", QVariant((qulonglong)(statistics.scriptValueCount)));
map.insert("scriptValueProxyCount", QVariant((qulonglong)(statistics.scriptValueProxyCount))); map.insert("scriptValueProxyCount", QVariant((qulonglong)(statistics.scriptValueProxyCount)));
#endif #endif
return map; return map;
} }
void ScriptManagerScriptingInterface::startCollectingObjectStatistics() {
_manager->engine()->startCollectingObjectStatistics();
}
void ScriptManagerScriptingInterface::dumpHeapObjectStatistics() {
_manager->engine()->dumpHeapObjectStatistics();
}
ScriptValue ScriptManagerScriptingInterface::createGarbageCollectorDebuggingObject() { ScriptValue ScriptManagerScriptingInterface::createGarbageCollectorDebuggingObject() {
//auto value = _manager->engine()->newQObject(new TestQObject, ScriptEngine::ScriptOwnership); //auto value = _manager->engine()->newQObject(new TestQObject, ScriptEngine::ScriptOwnership);
return _manager->engine()->newQObject(new TestQObject, ScriptEngine::ScriptOwnership); return _manager->engine()->newQObject(new TestQObject, ScriptEngine::ScriptOwnership);

View file

@ -53,7 +53,7 @@
class TestQObject : public QObject { class TestQObject : public QObject {
Q_OBJECT Q_OBJECT
public: public:
Q_INVOKABLE virtual void testMethod() { qDebug() << "TestQObject::testMethod"; }; //Q_INVOKABLE virtual void testMethod() { qDebug() << "TestQObject::testMethod"; };
}; };
class ScriptManagerScriptingInterface : public QObject { class ScriptManagerScriptingInterface : public QObject {
@ -510,6 +510,18 @@ public:
*/ */
Q_INVOKABLE QVariantMap getMemoryUsageStatistics(); 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 /**jsdoc
* Create test object for garbage collector debugging. * Create test object for garbage collector debugging.
* @function Script.createGarbageCollectorDebuggingObject() * @function Script.createGarbageCollectorDebuggingObject()

View file

@ -42,6 +42,8 @@
#include <Profile.h> #include <Profile.h>
#include <v8-profiler.h>
#include "../ScriptEngineLogging.h" #include "../ScriptEngineLogging.h"
#include "../ScriptProgram.h" #include "../ScriptProgram.h"
#include "../ScriptValue.h" #include "../ScriptValue.h"
@ -282,7 +284,11 @@ ScriptEngineV8::ScriptEngineV8(ScriptManager *manager) : ScriptEngine(manager),
// --assert-types // --assert-types
v8::V8::InitializeICU(); 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::V8::SetFlagsFromString("--stack-size=256 --single-threaded");
v8::Platform* platform = getV8Platform(); v8::Platform* platform = getV8Platform();
v8::V8::InitializePlatform(platform); v8::V8::InitializePlatform(platform);
@ -1720,10 +1726,31 @@ ScriptEngineMemoryStatistics ScriptEngineV8::getMemoryUsageStatistics() {
statistics.totalAvailableSize = heapStatistics.total_available_size(); statistics.totalAvailableSize = heapStatistics.total_available_size();
statistics.totalGlobalHandlesSize = heapStatistics.total_global_handles_size(); statistics.totalGlobalHandlesSize = heapStatistics.total_global_handles_size();
statistics.usedGlobalHandlesSize = heapStatistics.used_global_handles_size(); statistics.usedGlobalHandlesSize = heapStatistics.used_global_handles_size();
#ifdef OVERTE_V8_HANDLE_COUNTERS #ifdef OVERTE_V8_MEMORY_DEBUG
statistics.scriptValueCount = scriptValueCount; statistics.scriptValueCount = scriptValueCount;
statistics.scriptValueProxyCount = scriptValueProxyCount; statistics.scriptValueProxyCount = scriptValueProxyCount;
#endif #endif
return statistics; return statistics;
} }
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";
}
}
}

View file

@ -136,6 +136,8 @@ public: // ScriptEngine implementation
QString scriptValueDebugListMembersV8(const V8ScriptValue &v8Value); QString scriptValueDebugListMembersV8(const V8ScriptValue &v8Value);
virtual void logBacktrace(const QString &title = QString("")) override; virtual void logBacktrace(const QString &title = QString("")) override;
virtual ScriptEngineMemoryStatistics getMemoryUsageStatistics() 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 // 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); } 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(); void popContext();
// V8TODO: call this after initializing global object // V8TODO: call this after initializing global object
void storeGlobalObjectContents(); void storeGlobalObjectContents();
#ifdef OVERTE_V8_HANDLE_COUNTERS #ifdef OVERTE_V8_MEMORY_DEBUG
void incrementScriptValueCounter() { scriptValueCount++; }; void incrementScriptValueCounter() { scriptValueCount++; };
void decrementScriptValueCounter() { scriptValueCount--; }; void decrementScriptValueCounter() { scriptValueCount--; };
void incrementScriptValueProxyCounter() { scriptValueProxyCount++; }; void incrementScriptValueProxyCounter() { scriptValueProxyCount++; };
@ -264,7 +266,7 @@ protected:
//ArrayBufferClass* _arrayBufferClass; //ArrayBufferClass* _arrayBufferClass;
// Counts how many nested evaluate calls are there at a given point // Counts how many nested evaluate calls are there at a given point
int _evaluatingCounter; int _evaluatingCounter;
#ifdef OVERTE_V8_HANDLE_COUNTERS #ifdef OVERTE_V8_MEMORY_DEBUG
std::atomic<size_t> scriptValueCount{0}; std::atomic<size_t> scriptValueCount{0};
std::atomic<size_t> scriptValueProxyCount{0}; std::atomic<size_t> scriptValueProxyCount{0};
#endif #endif

View file

@ -47,7 +47,7 @@ public:
return; return;
} }
Q_ASSERT(false);*/ Q_ASSERT(false);*/
#ifdef OVERTE_V8_HANDLE_COUNTERS #ifdef OVERTE_V8_MEMORY_DEBUG
_engine->incrementScriptValueCounter(); _engine->incrementScriptValueCounter();
#endif #endif
_value.reset(new v8::UniquePersistent<T>(_engine->getIsolate(), value)); _value.reset(new v8::UniquePersistent<T>(_engine->getIsolate(), value));
@ -70,7 +70,7 @@ public:
v8::HandleScope handleScope(_engine->getIsolate()); v8::HandleScope handleScope(_engine->getIsolate());
v8::Context::Scope(_engine->getContext()); v8::Context::Scope(_engine->getContext());
//_value.reset(new v8::UniquePersistent<T>(_engine->getIsolate(), v8::Local<T>())); //_value.reset(new v8::UniquePersistent<T>(_engine->getIsolate(), v8::Local<T>()));
#ifdef OVERTE_V8_HANDLE_COUNTERS #ifdef OVERTE_V8_MEMORY_DEBUG
_engine->incrementScriptValueCounter(); _engine->incrementScriptValueCounter();
#endif #endif
_value.reset(new v8::UniquePersistent<T>(_engine->getIsolate(), v8::Local<T>())); _value.reset(new v8::UniquePersistent<T>(_engine->getIsolate(), v8::Local<T>()));
@ -82,7 +82,7 @@ public:
v8::HandleScope handleScope(_engine->getIsolate()); v8::HandleScope handleScope(_engine->getIsolate());
v8::Context::Scope(_engine->getContext()); v8::Context::Scope(_engine->getContext());
//_value.reset(new v8::UniquePersistent<T>(_engine->getIsolate(), copied.constGet())); //_value.reset(new v8::UniquePersistent<T>(_engine->getIsolate(), copied.constGet()));
#ifdef OVERTE_V8_HANDLE_COUNTERS #ifdef OVERTE_V8_MEMORY_DEBUG
_engine->incrementScriptValueCounter(); _engine->incrementScriptValueCounter();
#endif #endif
_value.reset(new v8::UniquePersistent<T>(_engine->getIsolate(), copied.constGet())); _value.reset(new v8::UniquePersistent<T>(_engine->getIsolate(), copied.constGet()));
@ -136,7 +136,7 @@ public:
v8::Isolate::Scope isolateScope(_engine->getIsolate()); v8::Isolate::Scope isolateScope(_engine->getIsolate());
v8::HandleScope handleScope(_engine->getIsolate()); v8::HandleScope handleScope(_engine->getIsolate());
//v8::Context::Scope(_engine->getContext()); //v8::Context::Scope(_engine->getContext());
#ifdef OVERTE_V8_HANDLE_COUNTERS #ifdef OVERTE_V8_MEMORY_DEBUG
_engine->decrementScriptValueCounter(); _engine->decrementScriptValueCounter();
#endif #endif
_value->Reset(); _value->Reset();