* Copyright (c) 2021 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ecmascript/dfx/hprof/heap_snapshot_json_serializer.h"
#include "ecmascript/dfx/hprof/heap_snapshot.h"
namespace panda::ecmascript {
bool HeapSnapshotJSONSerializer::Serialize(HeapSnapshot *snapshot, Stream *stream)
{
LOG_ECMA(INFO) << "HeapSnapshotJSONSerializer::Serialize begin";
ECMA_BYTRACE_NAME(HITRACE_LEVEL_COMMERCIAL, HITRACE_TAG_ARK, "HeapSnapshotJSONSerializer::Serialize", "");
ASSERT(snapshot->GetNodes() != nullptr && snapshot->GetEdges() != nullptr &&
snapshot->GetEcmaStringTable() != nullptr);
auto writer = new StreamWriter(stream);
SerializeSnapshotHeader(snapshot, writer);
SerializeNodes(snapshot, writer);
SerializeEdges(snapshot, writer);
SerializeTraceFunctionInfo(snapshot, writer);
SerializeTraceTree(snapshot, writer);
SerializeSamples(snapshot, writer);
SerializeLocations(writer);
SerializeStringTable(snapshot, writer);
SerializerSnapshotClosure(writer);
writer->End();
delete writer;
LOG_ECMA(INFO) << "HeapSnapshotJSONSerializer::Serialize exit";
return true;
}
bool HeapSnapshotJSONSerializer::SerializeExtraInfo(HeapSnapshot *snapshot, Stream *stream)
{
LOG_ECMA(INFO) << "HeapSnapshotJSONSerializer::SerializeExtraInfo begin";
ECMA_BYTRACE_NAME(HITRACE_LEVEL_COMMERCIAL, HITRACE_TAG_ARK,
"HeapSnapshotJSONSerializer::SerializeExtraInfo", "");
ASSERT(!snapshot->GetNodeAddressIdMap().empty());
auto writer = new IdMapWriter(stream);
SerializeNodeAddressIdMap(snapshot, writer);
writer->End();
delete writer;
LOG_ECMA(INFO) << "HeapSnapshotJSONSerializer::SerializeExtraInfo exit";
return true;
}
void HeapSnapshotJSONSerializer::SerializeSnapshotHeader(HeapSnapshot *snapshot, StreamWriter *writer)
{
writer->WriteString("{\"snapshot\":\n");
writer->WriteString("{\"meta\":\n");
writer->WriteString("{\"node_fields\":[\"type\",\"name\",\"id\",\"self_size\",\"edge_count\",\"trace_node_id\",");
writer->WriteString("\"detachedness\",\"native_size\"],\n");
writer->WriteString("\"node_types\":[[\"hidden\",\"array\",\"string\",\"object\",\"code\",\"closure\",\"regexp\",");
writer->WriteString("\"number\",\"native\",\"synthetic\",\"concatenated string\",\"slicedstring\",\"symbol\",");
writer->WriteString("\"bigint\",\"class\",\"framework\",\"handle\"],\"string\",\"number\",\"number\",");
writer->WriteString("\"number\",\"number\",\"number\"],\n");
writer->WriteString("\"edge_fields\":[\"type\",\"name_or_index\",\"to_node\"],\n");
writer->WriteString("\"edge_types\":[[\"context\",\"element\",\"property\",\"internal\",\"hidden\",\"shortcut\",");
writer->WriteString("\"weak\",\"native\",\"native_string\",\"xref\"],\"string_or_number\",\"node\"],\n");
writer->WriteString("\"trace_function_info_fields\":[\"function_id\",\"name\",\"script_name\",\"script_id\",");
writer->WriteString("\"line\",\"column\"],\n");
writer->WriteString("\"trace_node_fields\":[\"id\",\"function_info_index\",\"count\",\"size\",\"children\"],\n");
writer->WriteString("\"sample_fields\":[\"timestamp_us\",\"last_assigned_id\"],\n");
writer->WriteString("\"location_fields\":[\"object_index\",\"script_id\",\"line\",\"column\"]},\n\"node_count\":");
writer->WriteNumber(snapshot->GetNodeCount());
writer->WriteString(",\n\"edge_count\":");
writer->WriteNumber(snapshot->GetEdgeCount());
writer->WriteString(",\n\"trace_function_count\":");
writer->WriteNumber(snapshot->GetTrackAllocationsStack().size());
writer->WriteString("\n},\n");
}
void HeapSnapshotJSONSerializer::SerializeNodes(HeapSnapshot *snapshot, StreamWriter *writer)
{
LOG_ECMA(INFO) << "HeapSnapshotJSONSerializer::SerializeNodes";
ECMA_BYTRACE_NAME(HITRACE_LEVEL_COMMERCIAL, HITRACE_TAG_ARK, "HeapSnapshotJSONSerializer::SerializeNodes", "");
const CVector<HprofNode *> *nodes = snapshot->GetNodes();
const StringHashMap *stringTable = snapshot->GetEcmaStringTable();
ASSERT(nodes != nullptr);
writer->WriteString("\"nodes\":[");
size_t i = 0;
for (auto *node : *nodes) {
if (i > 0) {
writer->WriteChar(',');
}
writer->WriteNumber(static_cast<int>(node->GetType()));
writer->WriteChar(',');
writer->WriteNumber(stringTable->GetStringId(node->GetName()));
writer->WriteChar(',');
writer->WriteNumber(node->GetId());
writer->WriteChar(',');
writer->WriteNumber(node->GetSelfSize());
writer->WriteChar(',');
writer->WriteNumber(node->GetEdgeCount());
writer->WriteChar(',');
writer->WriteNumber(node->GetStackTraceId());
writer->WriteChar(',');
writer->WriteChar('0');
writer->WriteChar(',');
writer->WriteNumber(node->GetNativeSize());
if (i == nodes->size() - 1) {
writer->WriteString("],\n");
} else {
writer->WriteString("\n");
}
i++;
}
}
void HeapSnapshotJSONSerializer::SerializeEdges(HeapSnapshot *snapshot, StreamWriter *writer)
{
LOG_ECMA(INFO) << "HeapSnapshotJSONSerializer::SerializeEdges";
ECMA_BYTRACE_NAME(HITRACE_LEVEL_COMMERCIAL, HITRACE_TAG_ARK, "HeapSnapshotJSONSerializer::SerializeEdges", "");
const CVector<Edge *> *edges = snapshot->GetEdges();
const StringHashMap *stringTable = snapshot->GetEcmaStringTable();
ASSERT(edges != nullptr);
writer->WriteString("\"edges\":[");
size_t i = 0;
for (auto *edge : *edges) {
StringId nameOrIndex = edge->GetType() == EdgeType::ELEMENT ?
edge->GetIndex() : stringTable->GetStringId(edge->GetName());
if (i > 0) {
writer->WriteChar(',');
}
writer->WriteNumber(static_cast<int>(edge->GetType()));
writer->WriteChar(',');
writer->WriteNumber(nameOrIndex);
writer->WriteChar(',');
if (i == edges->size() - 1) {
writer->WriteNumber(edge->GetTo()->GetIndex() * HprofNode::NODE_FIELD_COUNT);
writer->WriteString("],\n");
} else {
writer->WriteNumber(edge->GetTo()->GetIndex() * HprofNode::NODE_FIELD_COUNT);
writer->WriteChar('\n');
}
i++;
}
}
void HeapSnapshotJSONSerializer::SerializeTraceFunctionInfo(HeapSnapshot *snapshot, StreamWriter *writer)
{
const CVector<FunctionInfo> trackAllocationsStack = snapshot->GetTrackAllocationsStack();
const StringHashMap *stringTable = snapshot->GetEcmaStringTable();
writer->WriteString("\"trace_function_infos\":[");
size_t i = 0;
for (const auto &info : trackAllocationsStack) {
if (i > 0) {
writer->WriteChar(',');
}
writer->WriteNumber(info.functionId);
writer->WriteChar(',');
CString functionName(info.functionName.c_str());
writer->WriteNumber(stringTable->GetStringId(&functionName));
writer->WriteChar(',');
CString scriptName(info.scriptName.c_str());
writer->WriteNumber(stringTable->GetStringId(&scriptName));
writer->WriteChar(',');
writer->WriteNumber(info.scriptId);
writer->WriteChar(',');
writer->WriteNumber(info.lineNumber);
writer->WriteChar(',');
writer->WriteNumber(info.columnNumber);
writer->WriteChar('\n');
i++;
}
writer->WriteString("],\n");
}
void HeapSnapshotJSONSerializer::SerializeTraceTree(HeapSnapshot *snapshot, StreamWriter *writer)
{
writer->WriteString("\"trace_tree\":[");
TraceTree* tree = snapshot->GetTraceTree();
if ((tree != nullptr) && (snapshot->trackAllocations())) {
SerializeTraceNode(tree->GetRoot(), writer);
}
writer->WriteString("],\n");
}
void HeapSnapshotJSONSerializer::SerializeTraceNode(TraceNode* node, StreamWriter *writer)
{
if (node == nullptr) {
return;
}
writer->WriteNumber(node->GetId());
writer->WriteChar(',');
writer->WriteNumber(node->GetNodeIndex());
writer->WriteChar(',');
writer->WriteNumber(node->GetTotalCount());
writer->WriteChar(',');
writer->WriteNumber(node->GetTotalSize());
writer->WriteString(",[");
int i = 0;
for (TraceNode* child : node->GetChildren()) {
if (i > 0) {
writer->WriteChar(',');
}
SerializeTraceNode(child, writer);
i++;
}
writer->WriteChar(']');
}
void HeapSnapshotJSONSerializer::SerializeSamples(HeapSnapshot *snapshot, StreamWriter *writer)
{
writer->WriteString("\"samples\":[");
const CVector<TimeStamp> &timeStamps = snapshot->GetTimeStamps();
if (!timeStamps.empty()) {
auto firstTimeStamp = timeStamps[0];
bool isFirst = true;
for (auto timeStamp : timeStamps) {
if (!isFirst) {
writer->WriteString("\n, ");
} else {
isFirst = false;
}
writer->WriteNumber(timeStamp.GetTimeStamp() - firstTimeStamp.GetTimeStamp());
writer->WriteString(", ");
writer->WriteNumber(timeStamp.GetLastSequenceId());
}
}
writer->WriteString("],\n");
}
void HeapSnapshotJSONSerializer::SerializeLocations(StreamWriter *writer)
{
writer->WriteString("\"locations\":[],\n");
}
void HeapSnapshotJSONSerializer::SerializeStringTable(HeapSnapshot *snapshot, StreamWriter *writer)
{
LOG_ECMA(INFO) << "HeapSnapshotJSONSerializer::SerializeStringTable";
ECMA_BYTRACE_NAME(HITRACE_LEVEL_COMMERCIAL, HITRACE_TAG_ARK,
"HeapSnapshotJSONSerializer::SerializeStringTable", "");
const StringHashMap *stringTable = snapshot->GetEcmaStringTable();
ASSERT(stringTable != nullptr);
writer->WriteString("\"strings\":[\"<dummy>\",\n");
writer->WriteString("\"\",\n");
writer->WriteString("\"GC roots\",\n");
size_t capcity = stringTable->GetCapcity();
ASSERT(capcity > 0);
size_t i = 0;
for (auto key : stringTable->GetOrderedKeyStorage()) {
if (i == capcity - 1) {
writer->WriteChar('\"');
SerializeString(stringTable->GetStringByKey(key), writer);
writer->WriteString("\"\n");
} else {
writer->WriteChar('\"');
SerializeString(stringTable->GetStringByKey(key), writer);
writer->WriteString("\",\n");
}
i++;
}
writer->WriteString("]\n");
}
void HeapSnapshotJSONSerializer::SerializeString(CString *str, StreamWriter *writer)
{
if (str == nullptr || writer == nullptr) {
return;
}
const char *s = str->c_str();
while (*s != '\0') {
if (*s == '\"' || *s == '\\') {
writer->WriteChar('\\');
writer->WriteChar(*s);
s++;
} else if (*s == '\n') {
writer->WriteString("\\n");
s++;
} else if (*s == '\b') {
writer->WriteString("\\b");
s++;
} else if (*s == '\f') {
writer->WriteString("\\f");
s++;
} else if (*s == '\r') {
writer->WriteString("\\r");
s++;
} else if (*s == '\t') {
writer->WriteString("\\t");
s++;
} else if (*s > ASCII_US && *s < ASCII_DEL) {
writer->WriteChar(*s);
s++;
} else if (*s <= ASCII_US || *s == ASCII_DEL) {
SerializeUnicodeChar(static_cast<uint32_t>(*s), writer);
s++;
} else {
size_t len = 1;
while (len <= UTF8_MAX_BYTES && *(s + len) != '\0') {
len++;
}
auto [unicode, bytes] =
common::utf_helper::ConvertUtf8ToUnicodeChar(reinterpret_cast<const uint8_t *>(s), len);
if (unicode == common::utf_helper::INVALID_UTF8) {
LOG_ECMA(WARN) << "HeapSnapshotJSONSerializer::SerializeString, str is not utf-8";
writer->WriteChar('?');
s++;
} else {
SerializeUnicodeChar(unicode, writer);
s += bytes;
}
}
}
}
void HeapSnapshotJSONSerializer::SerializeUnicodeChar(uint32_t unicodeChar, StreamWriter *writer)
{
static const char hexChars[] = "0123456789ABCDEF";
writer->WriteString("\\u");
writer->WriteChar(hexChars[(unicodeChar >> 0xC) & 0xF]);
writer->WriteChar(hexChars[(unicodeChar >> 0x8) & 0xF]);
writer->WriteChar(hexChars[(unicodeChar >> 0x4) & 0xF]);
writer->WriteChar(hexChars[unicodeChar & 0xF]);
}
void HeapSnapshotJSONSerializer::SerializerSnapshotClosure(StreamWriter *writer)
{
writer->WriteString("}\n");
}
void HeapSnapshotJSONSerializer::SerializeNodeAddressIdMap(HeapSnapshot *snapshot, StreamWriter *writer)
{
LOG_ECMA(INFO) << "HeapSnapshotJSONSerializer::SerializeNodeAddressIdMap";
ECMA_BYTRACE_NAME(HITRACE_LEVEL_COMMERCIAL, HITRACE_TAG_ARK,
"HeapSnapshotJSONSerializer::SerializeNodeAddressIdMap", "");
size_t i = 0;
writer->WriteString("{\"nodeIdMap\":[");
const CUnorderedMap<uintptr_t, NodeId>& nodeAddressIdMap = snapshot->GetNodeAddressIdMap();
for (auto node : nodeAddressIdMap) {
if (i > 0) {
writer->WriteChar(',');
}
writer->WriteString("{\"");
std::ostringstream oss;
const int width = sizeof(uintptr_t) * 2;
oss << "0x" << std::setfill('0') << std::setw(width) << std::uppercase << std::hex << node.first;
std::string address = oss.str();
writer->WriteString(address.c_str());
writer->WriteString("\":\"");
writer->WriteString(std::to_string(node.second).c_str());
writer->WriteString("\"}");
i++;
}
writer->WriteString("]}");
}
}