Add ability to take heap snapshots with python debugger

Signed-off-by: Ádám László Kulcsár <adam.kulcsar@szteszoftver.hu>
This commit is contained in:
Ádám László Kulcsár 2026-04-13 15:33:24 +02:00 committed by Patrick Kim
commit 769e86e32a
6 changed files with 39 additions and 14 deletions

View file

@ -1002,7 +1002,23 @@ bool DebuggerEscargot::processEvents(ExecutionState* state, Optional<ByteCodeBlo
}
case ESCARGOT_DEBUGGER_TAKE_HEAP_SNAPSHOT: {
HeapSnapshot snapshot;
snapshot.takeHeapSnapshot(state);
std::string fileName = snapshot.takeHeapSnapshot(state);
if (fileName.empty()) {
ESCARGOT_LOG_ERROR("Error happened during heap snapshot creation. Aborting now.\n");
abort();
} else if (enabled()) {
String* data = String::fromUTF8(fileName.c_str(), fileName.length());
if (data == nullptr) {
ESCARGOT_LOG_ERROR("Error happened during heap snapshot creation. Aborting now.\n");
abort();
}
send(ESCARGOT_DEBUGGER_SNAPSHOT_FINISHED,
data->characters8(),
data->length());
}
return true;
}
}

View file

@ -97,7 +97,8 @@ public:
ESCARGOT_MESSAGE_EXCEPTION_BACKTRACE = 46,
ESCARGOT_DEBUGGER_WAIT_FOR_SOURCE = 47,
ESCARGOT_DEBUGGER_WAITING_AFTER_PENDING = 48,
ESCARGOT_DEBUGGER_WAIT_FOR_WAIT_EXIT = 49
ESCARGOT_DEBUGGER_WAIT_FOR_WAIT_EXIT = 49,
ESCARGOT_DEBUGGER_SNAPSHOT_FINISHED = 50
};
// Messages sent by the debugger client to Escargot

View file

@ -267,7 +267,7 @@ void HeapSnapshot::addNotIndexedDeclarativeEnvironmentRecord(ExecutionState* sta
}
}
bool HeapSnapshot::prepareHeapSnapshotFile()
std::string HeapSnapshot::prepareHeapSnapshotFile()
{
auto addFields = [](rapidjson::Value& array, Vector<std::string, GCUtil::gc_malloc_allocator<std::string>>& strings, rapidjson::Document::AllocatorType& allocator) {
for (const std::string& elem : strings) {
@ -431,7 +431,7 @@ bool HeapSnapshot::prepareHeapSnapshotFile()
if (output.get() == nullptr) {
ESCARGOT_LOG_ERROR("Could not create heap snapshot file.\n");
return false;
return "";
}
char buffer[65536];
@ -439,11 +439,10 @@ bool HeapSnapshot::prepareHeapSnapshotFile()
rapidjson::Writer<rapidjson::FileWriteStream> writer(os);
jsonDoc.Accept(writer);
return true;
return outputName;
}
void HeapSnapshot::takeHeapSnapshot(ExecutionState* state)
std::string HeapSnapshot::takeHeapSnapshot(ExecutionState* state)
{
uint64_t id = 0;
uint64_t stateIdx = 0;
@ -524,10 +523,7 @@ void HeapSnapshot::takeHeapSnapshot(ExecutionState* state)
}
ESCARGOT_LOG_INFO("nodes: %ld | edges: %ld | strings: %ld", m_nodeCount, m_edgeCount, m_strings.size());
if (prepareHeapSnapshotFile() != true) {
ESCARGOT_LOG_ERROR("Error happened during heap snapshot creation. Aborting now.\n");
abort();
}
return prepareHeapSnapshotFile();
}
} // namespace Escargot
#endif

View file

@ -130,8 +130,8 @@ public:
void addIndexedDeclarativeEnvironmentRecord(ExecutionState* state, DeclarativeEnvironmentRecordIndexed* env, uint64_t& id, uint64_t& LexicalEnvIdx);
void addNotIndexedDeclarativeEnvironmentRecord(ExecutionState* state, DeclarativeEnvironmentRecordNotIndexed* env, uint64_t& id, uint64_t& LexicalEnvIdx);
bool prepareHeapSnapshotFile();
void takeHeapSnapshot(ExecutionState* state);
std::string prepareHeapSnapshotFile();
std::string takeHeapSnapshot(ExecutionState* state);
private:
uint64_t m_nodeCount;

View file

@ -197,6 +197,12 @@ class DebuggerPrompt(Cmd):
self.stop = True
do_bt = do_backtrace
def do_take_snapshot(self, args):
""" Save heap snapshot to a .snapshot file in the escargot binary's folder """
file_name = self.debugger._send_take_snapshot()
print("Took heap snapshot as \'" + file_name.decode('utf-8')[1:] + "\'!")
self.stop = True
def do_scope(self, args):
""" Get lexical environment chain """
self.debugger.scope_chain(args)

View file

@ -109,7 +109,8 @@ ESCARGOT_DEBUGGER_THERE_WAS_NO_SOURCE = 26
ESCARGOT_DEBUGGER_PENDING_CONFIG = 27
ESCARGOT_DEBUGGER_PENDING_RESUME = 28
ESCARGOT_DEBUGGER_WAIT_BEFORE_EXIT = 29
ESCARGOT_DEBUGGER_STOP = 30
ESCARGOT_DEBUGGER_TAKE_HEAP_SNAPSHOT = 30
ESCARGOT_DEBUGGER_STOP = 31
# Environment record types
@ -626,6 +627,11 @@ class Debugger(object):
1 if self.wait_exit else 0)
self.channel.send_message(self.byte_order, message)
def _send_take_snapshot(self):
message = struct.pack(self.byte_order + "BB", 1, ESCARGOT_DEBUGGER_TAKE_HEAP_SNAPSHOT)
self.channel.send_message(self.byte_order, message)
return self.channel.get_message(True)
def _exec_command(self, command_id):
message = struct.pack(self.byte_order + "BB",
1,